33 Concrete Example: Insert Comment and Replace Text Template

33.1 Motivation and Application Context

Developers perform repetitive tasks daily that are excellently suited for automation through extensions. One of the most common operations is the context-dependent insertion of comments or wrapping selected code parts with structured templates. These scenarios occur both in code documentation and in implementing recurring patterns.

In Eclipse, you were probably familiar with Live Templates or code generators that provide similar functionalities. While VSCode offers built-in snippets, a custom extension enables automations precisely adapted to your workflow. The following example demonstrates an extension that intelligently distinguishes between two contexts: inserting a TODO comment with an empty cursor and wrapping selected text with a structured template.

This functionality illustrates a fundamental aspect of extension development: context-dependent response to user input. The extension analyzes the current state of the editor and performs different operations based on this analysis. This pattern is found in nearly all productive extensions.

33.2 Example Objective

The example implements a command named extension.insertCommentOrTemplate that fulfills two distinct functions. With an empty cursor, it inserts a structured TODO comment at the current position. With an active text selection, it replaces the selected text with a template that embeds the original text in a commented block.

This dual functionality demonstrates important concepts of the VSCode API: analyzing selection states, conditional execution of different edit operations, and using template systems with placeholders. At the same time, the example shows how an extension can provide maximum utility with minimal complexity.

The technical implementation combines all concepts covered in the preceding chapters: command registration, editor access, selection analysis, and the TextEditorEdit API. Through this combination, you gain a complete understanding of the development workflow for VSCode extensions.

33.3 Complete Implementation

The extension consists of a registered command that encapsulates all logic in a single function. This architecture corresponds to the VSCode pattern for simple, focused extensions:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext): void {
    // Command registration in the activate function
    const command = vscode.commands.registerCommand(
        'extension.insertCommentOrTemplate', 
        async () => {
            // Access to the active editor with mandatory null check
            const editor = vscode.window.activeTextEditor;
            
            if (!editor) {
                // User-friendly error message when editor is missing
                vscode.window.showErrorMessage('No active editor found');
                return;
            }

            // Analyze selection state - core logic of context-dependent processing
            const selection = editor.selection;
            const selectedText = editor.document.getText(selection);

            // Edit operation with conditional logic for different contexts
            const success = await editor.edit(editBuilder => {
                if (selection.isEmpty) {
                    // Scenario A: Cursor without selection - insert TODO comment
                    handleEmptySelection(editBuilder, selection, editor);
                } else {
                    // Scenario B: Text selection - template-based replacement
                    handleTextSelection(editBuilder, selection, selectedText);
                }
            });

            // Success message for better user experience
            if (success) {
                const action = selection.isEmpty ? 'Comment inserted' : 'Template applied';
                vscode.window.showInformationMessage(action);
            } else {
                vscode.window.showErrorMessage('Operation failed');
            }
        }
    );

    // Register command for automatic deregistration on extension deactivation
    context.subscriptions.push(command);
}

/**
 * Handles the case of an empty cursor by inserting a TODO comment
 * @param editBuilder The VSCode TextEditorEdit builder for atomic operations
 * @param selection The current selection (empty, but containing position)
 * @param editor The active TextEditor for context information
 */
function handleEmptySelection(
    editBuilder: vscode.TextEditorEdit, 
    selection: vscode.Selection, 
    editor: vscode.TextEditor
): void {
    const position = selection.active;
    
    // Intelligent indentation based on the current line
    const currentLine = editor.document.lineAt(position.line);
    const indentation = currentLine.text.match(/^\s*/)?.[0] || '';
    
    // Timestamp for better tracking
    const timestamp = new Date().toISOString().substring(0, 10);
    
    // Structured comment with context
    const comment = `${indentation}// TODO (${timestamp}): Please add description\n`;
    
    editBuilder.insert(position, comment);
}

/**
 * Handles text selection through template-based replacement
 * @param editBuilder The VSCode TextEditorEdit builder
 * @param selection The current text selection
 * @param selectedText The selected text content
 */
function handleTextSelection(
    editBuilder: vscode.TextEditorEdit, 
    selection: vscode.Selection, 
    selectedText: string
): void {
    // Template with placeholder for dynamic content
    const template = `/* BEGIN BLOCK */\n{{TEXT}}\n/* END BLOCK */`;
    
    // Placeholder replacement - basis for more complex template systems
    const processedTemplate = template.replace('{{TEXT}}', selectedText);
    
    // Atomic replacement of the entire selection
    editBuilder.replace(selection, processedTemplate);
}

33.4 Technical Analysis of Implementation Flow

33.4.1 Command Registration and Extension Lifecycle

The extension follows the standard lifecycle of VSCode extensions. In the activate function, the command is registered and added to the context.subscriptions array. This registration ensures automatic cleanup on extension deactivation and prevents memory leaks.

Using an anonymous async function as command handler enables the use of await for asynchronous edit operations. This architecture is typical for extensions based on editor operations, as these fundamentally run asynchronously.

33.4.2 Selection Analysis and Conditional Processing

The core of the extension lies in analyzing the selection.isEmpty state. This boolean property distinguishes between a simple cursor and an active text selection. This distinction enables the implementation of context-dependent functionalities that intelligently adapt to user intent.

// Demonstration of selection logic
const selection = editor.selection;

if (selection.isEmpty) {
    // selection.active contains the cursor position
    // selection.start === selection.end === selection.active
    console.log(`Cursor at line ${selection.active.line}, column ${selection.active.character}`);
} else {
    // selection.start and selection.end span the selected range
    const selectedText = editor.document.getText(selection);
    console.log(`Selection: "${selectedText}" from ${selection.start.line}:${selection.start.character} to ${selection.end.line}:${selection.end.character}`);
}

33.4.3 Template System with Placeholder Replacement

The template system uses simple string replacement for the {{TEXT}} placeholder. This implementation can serve as a basis for more complex template engines:

// Advanced template processing for future development
function processAdvancedTemplate(template: string, context: { [key: string]: string }): string {
    let result = template;
    
    // Replace all placeholders in format {{KEY}}
    Object.entries(context).forEach(([key, value]) => {
        const placeholder = `{{${key}}}`;
        const regex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
        result = result.replace(regex, value);
    });
    
    return result;
}

// Application example for advanced templates
const advancedContext = {
    TEXT: selectedText,
    AUTHOR: 'Developer Name',
    DATE: new Date().toLocaleDateString('en-US'),
    PROJECT: vscode.workspace.name || 'Unknown Project'
};

const advancedTemplate = `/*
 * Created by: {{AUTHOR}}
 * Date: {{DATE}}
 * Project: {{PROJECT}}
 * 
 * {{TEXT}}
 */`;

33.4.4 Intelligent Indentation Handling

The handleEmptySelection function implements intelligent indentation through analysis of the current line. This pattern ensures that inserted code respects existing formatting:

// Detailed indentation analysis
function analyzeIndentation(editor: vscode.TextEditor, position: vscode.Position): string {
    const currentLine = editor.document.lineAt(position.line);
    const lineText = currentLine.text;
    
    // Extract leading whitespace characters
    const indentationMatch = lineText.match(/^(\s*)/);
    const existingIndentation = indentationMatch ? indentationMatch[1] : '';
    
    // Consider editor settings for consistent indentation
    const tabSize = editor.options.tabSize as number || 4;
    const insertSpaces = editor.options.insertSpaces as boolean || true;
    
    // For empty line beginning: use standard indentation
    if (existingIndentation.length === 0 && lineText.trim().length > 0) {
        return insertSpaces ? ' '.repeat(tabSize) : '\t';
    }
    
    return existingIndentation;
}

33.5 Error Handling and Robustness

33.5.1 Defensive Programming

The extension implements multi-layered error handling, ranging from basic null checks to specific edit validations:

export async function robustCommentInsertion(): Promise<void> {
    const editor = vscode.window.activeTextEditor;
    
    // Primary validation: editor availability
    if (!editor) {
        vscode.window.showErrorMessage('No active editor available');
        return;
    }
    
    // Secondary validation: document properties
    const document = editor.document;
    if (document.isUntitled && document.getText().length === 0) {
        const proceed = await vscode.window.showQuickPick(
            ['Yes, continue', 'No, cancel'],
            { placeHolder: 'Document is empty and unsaved. Continue?' }
        );
        
        if (proceed !== 'Yes, continue') {
            return;
        }
    }
    
    // Tertiary validation: position validity
    const selection = editor.selection;
    if (selection.active.line >= document.lineCount) {
        vscode.window.showErrorMessage('Invalid cursor position');
        return;
    }
    
    try {
        const success = await editor.edit(editBuilder => {
            // Edit operation with local error handling
            if (selection.isEmpty) {
                handleEmptySelection(editBuilder, selection, editor);
            } else {
                const selectedText = document.getText(selection);
                
                // Validation of selection
                if (selectedText.length > 10000) {
                    throw new Error('Text selection too large for template processing');
                }
                
                handleTextSelection(editBuilder, selection, selectedText);
            }
        });
        
        if (!success) {
            vscode.window.showErrorMessage('Edit operation was rejected by VSCode');
        }
        
    } catch (error) {
        vscode.window.showErrorMessage(`Processing error: ${error}`);
    }
}

33.5.2 Language-Specific Adaptations

A productive extension should adapt to the characteristics of the current programming language:

/**
 * Determines language-specific comment syntax
 * @param languageId The VSCode language ID of the active document
 * @returns Object with line and block comment syntax
 */
function getLanguageCommentStyle(languageId: string): { line: string; blockStart: string; blockEnd: string } {
    const commentStyles: { [key: string]: { line: string; blockStart: string; blockEnd: string } } = {
        'typescript': { line: '//', blockStart: '/*', blockEnd: '*/' },
        'javascript': { line: '//', blockStart: '/*', blockEnd: '*/' },
        'java': { line: '//', blockStart: '/*', blockEnd: '*/' },
        'python': { line: '#', blockStart: '"""', blockEnd: '"""' },
        'html': { line: '<!--', blockStart: '<!--', blockEnd: '-->' },
        'css': { line: '/*', blockStart: '/*', blockEnd: '*/' },
        'xml': { line: '<!--', blockStart: '<!--', blockEnd: '-->' }
    };
    
    // Fallback for unknown languages
    return commentStyles[languageId] || { line: '//', blockStart: '/*', blockEnd: '*/' };
}

/**
 * Language-dependent comment insertion
 */
function handleLanguageSpecificComment(
    editBuilder: vscode.TextEditorEdit, 
    selection: vscode.Selection, 
    editor: vscode.TextEditor
): void {
    const languageId = editor.document.languageId;
    const commentStyle = getLanguageCommentStyle(languageId);
    
    const position = selection.active;
    const currentLine = editor.document.lineAt(position.line);
    const indentation = currentLine.text.match(/^\s*/)?.[0] || '';
    const timestamp = new Date().toISOString().substring(0, 10);
    
    // Language-specific comment
    const comment = `${indentation}${commentStyle.line} TODO (${timestamp}): Please add description\n`;
    
    editBuilder.insert(position, comment);
}

33.6 Extension Ideas and Variations

33.6.1 Configurable Templates

A professional extension should support user configuration. VSCode provides the settings system for this:

/**
 * Loads user-defined templates from VSCode settings
 */
function loadUserTemplates(): { [key: string]: string } {
    const config = vscode.workspace.getConfiguration('commentExtension');
    
    // Default templates as fallback
    const defaultTemplates = {
        'block': '/* BEGIN BLOCK */\n{{TEXT}}\n/* END BLOCK */',
        'debug': '// DEBUG START\n{{TEXT}}\n// DEBUG END',
        'todo': '// TODO ({{DATE}}): {{TEXT}}'
    };
    
    // Merge user templates with default templates
    const userTemplates = config.get<{ [key: string]: string }>('templates', {});
    
    return { ...defaultTemplates, ...userTemplates };
}

/**
 * Template selection through QuickPick interface
 */
async function selectTemplate(): Promise<string | undefined> {
    const templates = loadUserTemplates();
    const templateNames = Object.keys(templates);
    
    if (templateNames.length === 1) {
        return templates[templateNames[0]];
    }
    
    const selectedTemplate = await vscode.window.showQuickPick(
        templateNames.map(name => ({
            label: name,
            description: templates[name].substring(0, 50) + '...'
        })),
        { placeHolder: 'Select template' }
    );
    
    return selectedTemplate ? templates[selectedTemplate.label] : undefined;
}

33.6.2 Multiple Selection Support

The extension can be extended to support multiple selections:

/**
 * Extended implementation with multiple selection support
 */
export async function handleMultipleSelections(): Promise<void> {
    const editor = vscode.window.activeTextEditor;
    if (!editor) return;
    
    const selections = editor.selections;
    
    // Analysis of selections
    const emptySelections = selections.filter(sel => sel.isEmpty);
    const textSelections = selections.filter(sel => !sel.isEmpty);
    
    if (selections.length > 1) {
        vscode.window.showInformationMessage(
            `Processing ${selections.length} selections: ${textSelections.length} with text, ${emptySelections.length} cursor-only`
        );
    }
    
    const success = await editor.edit(editBuilder => {
        // Processing in reverse order to preserve positions
        [...selections].reverse().forEach(selection => {
            if (selection.isEmpty) {
                handleEmptySelection(editBuilder, selection, editor);
            } else {
                const selectedText = editor.document.getText(selection);
                handleTextSelection(editBuilder, selection, selectedText);
            }
        });
    });
    
    if (success) {
        vscode.window.showInformationMessage(`${selections.length} selections processed successfully`);
    }
}

33.6.3 Integration with Git and Workspace Information

An extended extension can extract context from the current workspace:

/**
 * Gathers context information for extended templates
 */
async function gatherWorkspaceContext(): Promise<{ [key: string]: string }> {
    const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
    const activeEditor = vscode.window.activeTextEditor;
    
    const context: { [key: string]: string } = {
        DATE: new Date().toLocaleDateString('en-US'),
        TIME: new Date().toLocaleTimeString('en-US'),
        PROJECT: workspaceFolder?.name || 'Unknown Project',
        FILE: activeEditor?.document.fileName.split('/').pop() || 'Unknown File',
        LANGUAGE: activeEditor?.document.languageId || 'text'
    };
    
    // Add Git information (if available)
    try {
        const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports;
        if (gitExtension) {
            const git = gitExtension.getAPI(1);
            const repository = git.repositories[0];
            
            if (repository) {
                context.BRANCH = repository.state.HEAD?.name || 'main';
                context.COMMIT = repository.state.HEAD?.commit?.substring(0, 8) || 'unknown';
            }
        }
    } catch (error) {
        // Git information optional - ignore on errors
    }
    
    return context;
}

33.7 Outlook on Further Automation

33.7.1 Code Analysis and Intelligent Templates

Future versions of the extension could integrate code analysis to generate even more intelligent templates:

/**
 * Analyzes selected code for context-specific templates
 */
function analyzeCodeContext(selectedText: string, languageId: string): string {
    // Simple heuristics for code recognition
    const codePatterns = {
        function: /function\s+(\w+)\s*\(/,
        class: /class\s+(\w+)/,
        variable: /(const|let|var)\s+(\w+)/,
        import: /import\s+.*from\s+['"]/
    };
    
    for (const [type, pattern] of Object.entries(codePatterns)) {
        if (pattern.test(selectedText)) {
            return getCodeSpecificTemplate(type, selectedText);
        }
    }
    
    return getDefaultTemplate();
}

function getCodeSpecificTemplate(codeType: string, code: string): string {
    const templates = {
        function: `/**
 * Function: {{TEXT}}
 * @description Automatically generated documentation
 * @author {{AUTHOR}}
 * @date {{DATE}}
 */
{{TEXT}}`,
        class: `/**
 * Class: {{TEXT}}
 * @description {{TEXT}}
 * @version 1.0
 * @author {{AUTHOR}}
 */
{{TEXT}}`,
        variable: `// Variable {{TEXT}} - add description
{{TEXT}}`,
        import: `// Import: {{TEXT}}
{{TEXT}}`
    };
    
    return templates[codeType] || templates['function'];
}

33.7.2 Performance Optimizations for Large Files

When working with large files, performance considerations are important:

/**
 * Performance-optimized version for large selections
 */
async function optimizedTemplateApplication(
    selectedText: string, 
    template: string
): Promise<string> {
    // For large texts: streaming-based processing
    if (selectedText.length > 100000) {
        return processLargeTextInChunks(selectedText, template);
    }
    
    // Standard processing for normal sizes
    return template.replace('{{TEXT}}', selectedText);
}

async function processLargeTextInChunks(text: string, template: string): Promise<string> {
    const chunkSize = 10000;
    const chunks: string[] = [];
    
    // Split text into blocks
    for (let i = 0; i < text.length; i += chunkSize) {
        chunks.push(text.substring(i, i + chunkSize));
    }
    
    // Apply template to each block
    const processedChunks = chunks.map(chunk => 
        template.replace('{{TEXT}}', chunk)
    );
    
    return processedChunks.join('\n\n');
}