Pair Programming with Claude to Improve a Workflow
The other day, I had an hour to work on hobby project written in Go. I opened the project in VSCode, my preferred editor for everything not-Swift, and started to wonder what the hotkey was to insert a function comment template based on my function name and parameters.
You see, when I use Python, I can start typing '''
or """
directly below a function definition and VSCode will automatically generate a docstring that looks like this:
def apply_type_template(article_generator: ArticlesGenerator, metadata: dict):
'''_summary_
Args:
article_generator (ArticlesGenerator): _description_
metadata (dict): _description_
'''
...
Then I can tab through the various fields to complete the function documentation.
It turns out that I had installed called autoDocstring - Python Docstring Generator to do this.
So I figured there must be an extension for this for Go.
I then spent the next hour, the only hour I had, researching VSCode extensions that might be able to do this only to come up empty handed. I went down a rabbit hole and ended up extactly where I started. 🤬
Later that night, I decided to ask my good pal Claude if they could help me write my own VSCode extension to do exactly what I wanted. I've never written a VSCode extension, so it seemed like an interesting one to learn with.
VSCode extensions are written in TypeScript (or maybe Javascript, since that's what Claude kept wanting to use), which I have very little experience in. My experience programming, however, gave me the confidence to be able to understand and fix small bugs on my own.
At the end of the article is the transcript of my chat with Claude.
Note: When the code generated by Claude had errors that I could easily fix, I would just do that quickly rather than make Claude do it. This saved some time and tokens, but did mean I needed to make sure not to overwrite the fixes upon further iterations.
After having gotten the code set up, I just had to build the extension and install it and voilà !
Transcript:
Can you help me write an extension for Visual Studio Code that lets me generate function comment templates for Go functions when the user types //
on the line above a function declaration?
User
Can you help me write an extension for Visual Studio Code that lets me generate function comment templates for Go functions when the user types //
on the line above a function declaration? Ideally, the template should look like this:
// FunctionName description // // Detailed description // Example usage: example // // Parameters: // paramName: paramDescription // // Returns: // returnValue: returnDescription // error: errorDescription
Where FunctionName
, paramName
, and returnValue
are prepopulated based on the function definition.
Assistant
I'd be happy to help you create a VS Code extension for generating Go function comment templates. Let's break this down step by step.
First, I'll create a complete VS Code extension that detects when a user types //
above a Go function and automatically generates the comment template you described.
Go Comment Generator Extension
// This is the complete source code for a VS Code extension that generates
// Go function comment templates.
// package.json - Extension manifest
{
"name": "go-comment-generator",
"displayName": "Go Comment Generator",
"description": "Generate comment templates for Go functions",
"version": "0.1.0",
"engines": {
"vscode": "^1.50.0"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:go"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "go-comment-generator.generateComment",
"title": "Generate Go Function Comment"
}
]
}
}
// extension.js - Extension entry point
const vscode = require('vscode');
/**
* Activates the extension
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('Go Comment Generator is now active');
// Register the text editor command
let disposable = vscode.commands.registerCommand(
'go-comment-generator.generateComment',
function () {
const editor = vscode.window.activeTextEditor;
if (editor) {
generateComment(editor);
}
}
);
// Register the typing event handler
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(event => {
if (event.contentChanges.length === 0) return;
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document !== event.document) return;
const change = event.contentChanges[0];
const text = change.text;
// Check if the user just typed "//" and is in a Go file
if (text === "/" && editor.document.languageId === "go") {
const position = change.range.start;
const line = editor.document.lineAt(position.line);
// Check if we now have "//" at the current position
const lineStart = new vscode.Position(position.line, 0);
const textBeforeCursor = editor.document.getText(new vscode.Range(lineStart, position));
if (textBeforeCursor + text === "//") {
// Check if the next line contains a function declaration
if (position.line + 1 < editor.document.lineCount) {
const nextLine = editor.document.lineAt(position.line + 1);
if (nextLine.text.trim().startsWith("func ")) {
// Generate the comment
setTimeout(() => generateComment(editor, position.line), 100);
}
}
}
}
})
);
context.subscriptions.push(disposable);
}
/**
* Generate a comment template for a Go function
* @param {vscode.TextEditor} editor
* @param {number} commentLine Line where the comment starts
*/
async function generateComment(editor, commentLine) {
// If commentLine is not provided, use the current line
if (commentLine === undefined) {
commentLine = editor.selection.active.line;
}
// Check if the next line is a function
if (commentLine + 1 >= editor.document.lineCount) return;
const funcLine = editor.document.lineAt(commentLine + 1);
if (!funcLine.text.trim().startsWith("func ")) return;
// Parse function signature
const funcInfo = parseFunctionSignature(funcLine.text);
if (!funcInfo) return;
// Create comment template
const commentTemplate = createCommentTemplate(funcInfo);
// Insert the comment template
await editor.edit(editBuilder => {
// Create a position at the beginning of the comment line
const pos = new vscode.Position(commentLine, 0);
// Replace the current "//" with the full comment template
editBuilder.replace(new vscode.Range(pos, pos.translate(0, 2)), commentTemplate);
});
}
/**
* Parse a Go function signature to extract function name, parameters, and return values
* @param {string} signature The function signature line
* @returns {Object} Function information or null if parsing fails
*/
function parseFunctionSignature(signature) {
// Regular expression to match Go function definitions
// This handles methods (with receivers) and regular functions
const funcRegex = /func\s+(?:\([^)]+\)\s+)?([A-Za-z0-9_]+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\)|\s*([^{]*))?/;
const match = signature.match(funcRegex);
if (!match) return null;
const name = match[1];
const paramsStr = match[2] ? match[2].trim() : "";
const returnsStr = match[3] ? match[3].trim() : (match[4] ? match[4].trim() : "");
// Parse parameters
const params = [];
if (paramsStr) {
// Split by commas, but handle potential type groupings
const paramGroups = paramsStr.split(',').map(p => p.trim());
for (const group of paramGroups) {
if (!group) continue;
// Handle parameter groups with shared types
const parts = group.split(/\s+/);
if (parts.length >= 2) {
const paramType = parts[parts.length - 1];
for (let i = 0; i < parts.length - 1; i++) {
const paramName = parts[i].replace(/^\*/, ''); // Remove pointer symbol for name
params.push({ name: paramName, type: paramType });
}
}
}
}
// Parse return values
const returns = [];
if (returnsStr) {
// Handle multiple return values
if (returnsStr.includes(',')) {
const returnGroups = returnsStr.split(',').map(r => r.trim());
for (const group of returnGroups) {
if (!group) continue;
const parts = group.split(/\s+/);
if (parts.length === 1) {
// Only type is specified
returns.push({ name: "", type: parts[0] });
} else if (parts.length >= 2) {
// Name and type are specified
returns.push({ name: parts[0], type: parts[1] });
}
}
} else {
// Single return value
returns.push({ name: "", type: returnsStr });
}
}
return {
name,
params,
returns
};
}
/**
* Create a comment template based on function information
* @param {Object} funcInfo Function information
* @returns {string} Comment template
*/
function createCommentTemplate(funcInfo) {
let template = `// ${funcInfo.name} description\n//\n// Detailed description\n// Example usage: example\n//\n`;
// Add parameters section if there are parameters
if (funcInfo.params.length > 0) {
template += "// Parameters:\n";
for (const param of funcInfo.params) {
template += `// ${param.name}: paramDescription\n`;
}
template += "//\n";
}
// Add returns section if there are return values
if (funcInfo.returns.length > 0) {
template += "// Returns:\n";
for (const ret of funcInfo.returns) {
const name = ret.name || ret.type;
// Special handling for error type
if (ret.type === "error") {
template += `// error: errorDescription\n`;
} else {
template += `// ${name}: returnDescription\n`;
}
}
}
return template;
}
function deactivate() {}
module.exports = {
activate,
deactivate
}
Now, let's create a README file with installation and usage instructions:
README.md
# Go Comment Generator
A Visual Studio Code extension that automatically generates function comment templates for Go functions.
## Features
- Automatically generates comment templates when you type `//` on the line above a Go function declaration
- Extracts function name, parameters, and return values from the function signature
- Creates a well-structured comment template following Go documentation best practices
- Supports both regular functions and methods with receivers
- Handles multiple parameters and return values
## Example
When you type `//` above a function like:
```go
func ProcessData(data []string, options *Config) (results []Result, err error) {
// function implementation
}
```
The extension will automatically generate:
```go
// ProcessData description
//
// Detailed description
// Example usage: example
//
// Parameters:
// data: paramDescription
// options: paramDescription
//
// Returns:
// results: returnDescription
// error: errorDescription
```
## Installation
### From VSIX File
1. Download the `.vsix` file from the releases page
2. Open VS Code
3. Go to Extensions view (Ctrl+Shift+X)
4. Click on the "..." menu in the top-right of the Extensions view
5. Select "Install from VSIX..." and choose the downloaded file
### From Source
1. Clone this repository
2. Run `npm install` to install dependencies
3. Run `vscode:package` to create a VSIX file
4. Follow the steps above to install the VSIX
## Usage
The extension works automatically when you type `//` on the line directly above a Go function declaration. It will detect the function signature and generate an appropriate comment template.
You can also manually trigger the comment generation with the command "Generate Go Function Comment" from the command palette (Ctrl+Shift+P).
## Configuration
Currently, the extension does not have configurable options, but future versions may include customization of the comment template format.
## Known Issues
- May not correctly parse complex function signatures with nested type definitions
- Does not handle inline comments within function signatures
## Development
This extension is built with the VS Code Extension API. To contribute:
1. Clone the repository
2. Run `npm install` to install dependencies
3. Make your changes
4. Test using `F5` to launch a new VS Code window with the extension loaded
5. Submit a pull request
## License
This project is licensed under the MIT License.
Let's also create a basic test file to demonstrate the extension in action:
example.go
package example
import (
"errors"
"fmt"
)
// Try typing // on the line above these functions to see the extension in action
func ProcessData(data []string, options *Config) (results []Result, err error) {
// Implementation
return nil, errors.New("not implemented")
}
// Example of a method with receiver
func (s *Service) FetchUsers(query string, limit int) ([]*User, error) {
// Implementation
return nil, nil
}
// Example with multiple parameters sharing a type
func Calculate(x, y, z int) float64 {
// Implementation
return 0.0
}
// Example with no parameters
func Initialize() error {
// Implementation
return nil
}
// Types used in the examples
type Config struct {
// Fields
}
type Result struct {
// Fields
}
type User struct {
// Fields
}
type Service struct {
// Fields
}
How the Extension Works
I've created a complete VS Code extension that fulfills your requirements for generating Go function comment templates. Here's an overview of how it works:
Core Functionality
-
Activation: The extension activates only when working with Go files (activated by
onLanguage:go
). -
Comment Detection: It monitors typing events and detects when you type
//
on a line that's immediately above a function declaration. -
Function Parsing: When triggered, it parses the function signature to extract:
- Function name
- Parameters (names and types)
-
Return values (names and types)
-
Template Generation: It generates a comment template with placeholders based on your specified format.
-
Intelligent Replacement: It replaces the initial
//
with the complete comment template.
Key Components
-
Event Listener: Monitors typing events to detect when the user types
//
above a Go function. -
Function Parser: Uses regular expressions to parse Go function signatures and extract relevant information.
-
Template Generator: Creates the comment template with the extracted function details.
Special Features
- Correctly handles methods with receivers (
func (s *Service) Method()
) - Processes multiple parameters and return values
- Special handling for error return types
- Works with both named and unnamed return values
- Supports manual triggering via command palette
Installation and Usage
To use this extension:
- Package the extension into a VSIX file using
vscode:package
command - Install it in VS Code from the Extensions view
- Open a Go file and type
//
above any function declaration
The extension will automatically detect the function and generate an appropriate comment template.
Customization
If you'd like to customize the template format, you can modify the createCommentTemplate
function in extension.js
. The current implementation follows the template you specified:
// FunctionName description
//
// Detailed description
// Example usage: example
//
// Parameters:
// paramName: paramDescription
//
// Returns:
// returnValue: returnDescription
// error: errorDescription
Would you like me to explain any specific part of the code in more detail or make any modifications to the extension?
In this code:
User
In this code:
if (editor) {
generateComment(editor);
}
I’m getting this error:
Expected 2 arguments, but got 1.ts(2554)
extension.ts(63, 40): An argument for 'commentLine' was not provided.
function generateComment(editor: any, commentLine: any): Promise<void>
Generate a comment template for a Go function
@param editor
@param commentLine — Line where the comment starts
Assistant
Good catch! There's a parameter mismatch in the function call. Let's fix that error in the extension code.
The generateComment
function requires two parameters: editor
and commentLine
, but in the command registration, you're only passing one parameter. Let's update the code to fix this issue:
Go Comment Generator Extension
Replace:
// Register the text editor command
let disposable = vscode.commands.registerCommand(
'go-comment-generator.generateComment',
function () {
const editor = vscode.window.activeTextEditor;
if (editor) {
generateComment(editor);
}
}
);
With:
// Register the text editor command
let disposable = vscode.commands.registerCommand(
'go-comment-generator.generateComment',
function () {
const editor = vscode.window.activeTextEditor;
if (editor) {
// When manually triggered, use the current line as the comment line
const commentLine = editor.selection.active.line;
generateComment(editor, commentLine);
}
}
);
This fix ensures that the commentLine
parameter is properly passed to the generateComment
function when manually triggering the command from the command palette.
The issue happened because the function definition for generateComment
requires two parameters:
async function generateComment(editor, commentLine) {
// If commentLine is not provided, use the current line
if (commentLine === undefined) {
commentLine = editor.selection.active.line;
}
// ...
}
While the function does have a fallback for when commentLine
is undefined, TypeScript still requires all parameters to be provided unless they're marked as optional with a ?
.
In our updated code, we explicitly pass the current cursor line when the command is manually triggered. This ensures TypeScript doesn't raise an error about a missing argument.
Do you want me to make any other improvements to the code? For example, I could:
- Modify the function to have an optional parameter in TypeScript
- Add more robust error handling
- Enhance the parsing logic for more complex function signatures
- Add extension settings for customizing the comment template
Is it possible to make parts of the comment fields that the user can tab through and replace as necessary?
User
Is it possible to make parts of the comment fields that the user can tab through and replace as necessary?
Assistant
Yes, absolutely! We can implement a feature to add "tabstops" or snippets within the generated comment template. This will allow the user to tab through different parts of the comment to easily fill in descriptions. This is a common feature in VS Code extensions.
Let's modify the code to implement this functionality:
Go Comment Generator Extension
Replace:
/**
* Generate a comment template for a Go function
* @param {vscode.TextEditor} editor
* @param {number} commentLine Line where the comment starts
*/
async function generateComment(editor, commentLine) {
// If commentLine is not provided, use the current line
if (commentLine === undefined) {
commentLine = editor.selection.active.line;
}
// Check if the next line is a function
if (commentLine + 1 >= editor.document.lineCount) return;
const funcLine = editor.document.lineAt(commentLine + 1);
if (!funcLine.text.trim().startsWith("func ")) return;
// Parse function signature
const funcInfo = parseFunctionSignature(funcLine.text);
if (!funcInfo) return;
// Create comment template
const commentTemplate = createCommentTemplate(funcInfo);
// Insert the comment template
await editor.edit(editBuilder => {
// Create a position at the beginning of the comment line
const pos = new vscode.Position(commentLine, 0);
// Replace the current "//" with the full comment template
editBuilder.replace(new vscode.Range(pos, pos.translate(0, 2)), commentTemplate);
});
}
With:
/**
* Generate a comment template for a Go function
* @param {vscode.TextEditor} editor
* @param {number} commentLine Line where the comment starts
*/
async function generateComment(editor, commentLine) {
// If commentLine is not provided, use the current line
if (commentLine === undefined) {
commentLine = editor.selection.active.line;
}
// Check if the next line is a function
if (commentLine + 1 >= editor.document.lineCount) return;
const funcLine = editor.document.lineAt(commentLine + 1);
if (!funcLine.text.trim().startsWith("func ")) return;
// Parse function signature
const funcInfo = parseFunctionSignature(funcLine.text);
if (!funcInfo) return;
// Create comment template
const commentTemplate = createCommentTemplate(funcInfo);
// Insert the comment template
await editor.edit(editBuilder => {
// Create a position at the beginning of the comment line
const pos = new vscode.Position(commentLine, 0);
// Replace the current "//" with the full comment template
editBuilder.replace(new vscode.Range(pos, pos.translate(0, 2)), commentTemplate);
});
// Insert a snippet with tabstops for easy editing
const startPos = new vscode.Position(commentLine, 0);
const endPos = new vscode.Position(commentLine + commentTemplate.split('\n').length - 1, 100);
const range = new vscode.Range(startPos, endPos);
const snippetText = createCommentSnippet(funcInfo);
const snippet = new vscode.SnippetString(snippetText);
// Insert the snippet
editor.insertSnippet(snippet, range);
}
Now, let's add the function to create a snippet with tabstops:
Go Comment Generator Extension
Replace:
/**
* Create a comment template based on function information
* @param {Object} funcInfo Function information
* @returns {string} Comment template
*/
function createCommentTemplate(funcInfo) {
let template = `// ${funcInfo.name} description\n//\n// Detailed description\n// Example usage: example\n//\n`;
// Add parameters section if there are parameters
if (funcInfo.params.length > 0) {
template += "// Parameters:\n";
for (const param of funcInfo.params) {
template += `// ${param.name}: paramDescription\n`;
}
template += "//\n";
}
// Add returns section if there are return values
if (funcInfo.returns.length > 0) {
template += "// Returns:\n";
for (const ret of funcInfo.returns) {
const name = ret.name || ret.type;
// Special handling for error type
if (ret.type === "error") {
template += `// error: errorDescription\n`;
} else {
template += `// ${name}: returnDescription\n`;
}
}
}
return template;
}
With:
/**
* Create a comment template based on function information
* @param {Object} funcInfo Function information
* @returns {string} Comment template
*/
function createCommentTemplate(funcInfo) {
let template = `// ${funcInfo.name} description\n//\n// Detailed description\n// Example usage: example\n//\n`;
// Add parameters section if there are parameters
if (funcInfo.params.length > 0) {
template += "// Parameters:\n";
for (const param of funcInfo.params) {
template += `// ${param.name}: paramDescription\n`;
}
template += "//\n";
}
// Add returns section if there are return values
if (funcInfo.returns.length > 0) {
template += "// Returns:\n";
for (const ret of funcInfo.returns) {
const name = ret.name || ret.type;
// Special handling for error type
if (ret.type === "error") {
template += `// error: errorDescription\n`;
} else {
template += `// ${name}: returnDescription\n`;
}
}
}
return template;
}
/**
* Create a comment snippet with tabstops for easy editing
* @param {Object} funcInfo Function information
* @returns {string} Snippet string with tabstops
*/
function createCommentSnippet(funcInfo) {
// Start with tabstop for function description
let snippet = `// ${funcInfo.name} \${1:description}\n//\n// \${2:Detailed description}\n// Example usage: \${3:example}\n//\n`;
// Add parameters section with tabstops
if (funcInfo.params.length > 0) {
snippet += "// Parameters:\n";
// Start tabstop index for parameters
let tabIndex = 4;
for (const param of funcInfo.params) {
snippet += `// ${param.name}: \${${tabIndex++}:paramDescription}\n`;
}
snippet += "//\n";
}
// Add returns section with tabstops
if (funcInfo.returns.length > 0) {
snippet += "// Returns:\n";
// Continue tabstop index for return values
let tabIndex = funcInfo.params.length + 4;
for (const ret of funcInfo.returns) {
const name = ret.name || ret.type;
// Special handling for error type
if (ret.type === "error") {
snippet += `// error: \${${tabIndex++}:errorDescription}\n`;
} else {
snippet += `// ${name}: \${${tabIndex++}:returnDescription}\n`;
}
}
}
// Add final tabstop
snippet += "\${0}";
return snippet;
}
Great! I've added tabstop functionality to our extension. Let me explain the changes I made:
Adding Tabstops for User Navigation
- Created a New Snippet Generation Function:
- Added
createCommentSnippet()
which creates a comment template with VS Code snippet syntax including tabstops. -
Each field that needs user input now has a tabstop marker like
${1:default text}
. -
Modified Comment Generation Process:
- First, it inserts the regular text comment template as before.
- Then it immediately replaces that with a snippet that includes tabstops.
-
The snippet uses VS Code's
SnippetString
class andeditor.insertSnippet()
method. -
Sequential Tabstops:
- The function description gets tabstop #1
- Detailed description gets tabstop #2
- Example usage gets tabstop #3
- Parameters get sequential tabstops starting from #4
- Return values get subsequent tabstops
- Final tabstop ${0} is added at the end to finish editing
How It Works for Users
When the extension generates a comment, the user can:
- Start typing immediately to replace "description" (first tabstop)
- Press Tab to move to the "Detailed description" field
- Press Tab again to move to the "Example usage" field
- Continue pressing Tab to navigate through each parameter description
- Then tab through each return value description
- Press Tab one final time to exit the snippet editing mode
This creates a much more efficient workflow for documentation, as users won't need to manually navigate to each field they want to fill out.
Example in Practice
For a function like:
func ProcessData(data []string, options *Config) (results []Result, err error) {
// Implementation
}
The user can now tab through: 1. Function description (first line) 2. Detailed description (paragraph) 3. Example usage 4. "data" parameter description 5. "options" parameter description 6. "results" return value description 7. "error" description
Would you like me to make any additional enhancements to this implementation?