Pair Programming with Claude to Improve a Workflow

07 May 2025

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

  1. Activation: The extension activates only when working with Go files (activated by onLanguage:go).

  2. Comment Detection: It monitors typing events and detects when you type // on a line that's immediately above a function declaration.

  3. Function Parsing: When triggered, it parses the function signature to extract:

  4. Function name
  5. Parameters (names and types)
  6. Return values (names and types)

  7. Template Generation: It generates a comment template with placeholders based on your specified format.

  8. Intelligent Replacement: It replaces the initial // with the complete comment template.

Key Components

  1. Event Listener: Monitors typing events to detect when the user types // above a Go function.

  2. Function Parser: Uses regular expressions to parse Go function signatures and extract relevant information.

  3. 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:

  1. Package the extension into a VSIX file using vscode:package command
  2. Install it in VS Code from the Extensions view
  3. 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:

  1. Modify the function to have an optional parameter in TypeScript
  2. Add more robust error handling
  3. Enhance the parsing logic for more complex function signatures
  4. 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

  1. Created a New Snippet Generation Function:
  2. Added createCommentSnippet() which creates a comment template with VS Code snippet syntax including tabstops.
  3. Each field that needs user input now has a tabstop marker like ${1:default text}.

  4. Modified Comment Generation Process:

  5. First, it inserts the regular text comment template as before.
  6. Then it immediately replaces that with a snippet that includes tabstops.
  7. The snippet uses VS Code's SnippetString class and editor.insertSnippet() method.

  8. Sequential Tabstops:

  9. The function description gets tabstop #1
  10. Detailed description gets tabstop #2
  11. Example usage gets tabstop #3
  12. Parameters get sequential tabstops starting from #4
  13. Return values get subsequent tabstops
  14. 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:

  1. Start typing immediately to replace "description" (first tabstop)
  2. Press Tab to move to the "Detailed description" field
  3. Press Tab again to move to the "Example usage" field
  4. Continue pressing Tab to navigate through each parameter description
  5. Then tab through each return value description
  6. 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?


Tagged with: