VSCode扩展实现:支持多语言的编码Agent集成

本部分包含VSCode扩展的完整实现,包括多语言支持、命令注册、API调用和自动补全功能。

1. VSCode扩展目录结构

vscode-extension/
├── extension.js       # 扩展主文件
├── i18n.js            # 国际化模块
├── package.json       # 扩展配置
└── locales/           # 语言文件
    ├── en.json
    ├── zh-CN.json
    └── ja.json

2. 国际化模块 (i18n.js)

// vscode-extension/i18n.js
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');

class I18nManager {
    constructor() {
        this.translations = {};
        this.currentLang = 'en';
        this.loadTranslations();
    }

    loadTranslations() {
        // 获取VSCode的语言设置
        const vscodeLang = vscode.env.language;
        
        // 映射VSCode语言代码到我们的语言文件
        const langMap = {
            'zh-cn': 'zh-CN',
            'zh-tw': 'zh-CN', // 简化为zh-CN
            'ja': 'ja',
            'en': 'en',
            'en-us': 'en'
        };
        
        this.currentLang = langMap[vscodeLang.toLowerCase()] || 'en';
        
        // 加载语言文件
        const localesDir = path.join(__dirname, 'locales');
        const supportedLangs = ['en', 'zh-CN', 'ja'];
        
        supportedLangs.forEach(lang => {
            try {
                const filePath = path.join(localesDir, `${lang}.json`);
                const content = fs.readFileSync(filePath, 'utf8');
                this.translations[lang] = JSON.parse(content);
            } catch (error) {
                console.error(`Failed to load language file for ${lang}:`, error);
            }
        });
        
        // 如果当前语言没有翻译,使用英文
        if (!this.translations[this.currentLang]) {
            this.currentLang = 'en';
        }
    }

    get(key, params = {}) {
        const keys = key.split('.');
        let translation = this.translations[this.currentLang];
        
        // 逐级获取嵌套的值
        for (const k of keys) {
            if (translation && typeof translation === 'object') {
                translation = translation[k];
            } else {
                break;
            }
        }
        
        // 如果找不到,尝试英文
        if (!translation) {
            translation = this.translations['en'];
            for (const k of keys) {
                if (translation && typeof translation === 'object') {
                    translation = translation[k];
                } else {
                    break;
                }
            }
        }
        
        // 如果还是找不到,返回key
        if (!translation) {
            return key;
        }
        
        // 处理参数替换
        return translation.replace(/\{(\w+)\}/g, (match, key) => {
            return params.hasOwnProperty(key) ? params[key] : match;
        });
    }
    
    getCurrentLanguage() {
        return this.currentLang;
    }
}

// 创建全局实例
const i18n = new I18nManager();

// 导出便捷函数
function localize(key, params) {
    return i18n.get(key, params);
}

module.exports = {
    i18n,
    localize
};

3. 语言文件 (locales)

en.json

{
  "extension": {
    "activated": "Coding Agent extension activated",
    "ready": "Agent Ready",
    "completing": "Completing...",
    "generating": "Generating...",
    "explaining": "Explaining...",
    "refactoring": "Refactoring...",
    "debugging": "Debugging...",
    "generatingTests": "Generating tests...",
    "error": "Error",
    "disconnected": "Disconnected",
    "connected": "Coding Agent backend connected",
    "cannotConnect": "Cannot connect to Coding Agent backend, please ensure the service is started"
  },
  "commands": {
    "complete": "Agent: Complete Code",
    "generate": "Agent: Generate Code",
    "explain": "Agent: Explain Code",
    "refactor": "Agent: Refactor Code",
    "debug": "Agent: Debug Code",
    "test": "Agent: Generate Tests"
  },
  "prompts": {
    "generateDescription": "Describe the code you want to generate",
    "generatePlaceholder": "e.g., Create a REST API endpoint",
    "selectCode": "Please select code first",
    "completeFailed": "Completion failed: {error}",
    "generateFailed": "Generation failed: {error}",
    "explainFailed": "Explanation failed: {error}",
    "refactorFailed": "Refactoring failed: {error}",
    "debugFailed": "Debug failed: {error}",
    "testFailed": "Test generation failed: {error}",
    "refactorComplete": "Refactoring complete: {suggestions}"
  },
  "output": {
    "explanation": "Code Explanation",
    "debugAnalysis": "Debug Analysis",
    "aiGenerated": "AI Generated",
    "agentSuggestion": "Coding Agent Suggestion"
  }
}

zh-CN.json

{
  "extension": {
    "activated": "编码助手扩展已激活",
    "ready": "助手就绪",
    "completing": "正在补全...",
    "generating": "正在生成...",
    "explaining": "正在解释...",
    "refactoring": "正在重构...",
    "debugging": "正在调试...",
    "generatingTests": "正在生成测试...",
    "error": "错误",
    "disconnected": "未连接",
    "connected": "编码助手后端已连接",
    "cannotConnect": "无法连接到编码助手后端,请确保服务已启动"
  },
  "commands": {
    "complete": "助手: 补全代码",
    "generate": "助手: 生成代码",
    "explain": "助手: 解释代码",
    "refactor": "助手: 重构代码",
    "debug": "助手: 调试代码",
    "test": "助手: 生成测试"
  },
  "prompts": {
    "generateDescription": "描述你想生成的代码",
    "generatePlaceholder": "例如:创建一个REST API端点",
    "selectCode": "请先选择代码",
    "completeFailed": "补全失败: {error}",
    "generateFailed": "生成失败: {error}",
    "explainFailed": "解释失败: {error}",
    "refactorFailed": "重构失败: {error}",
    "debugFailed": "调试失败: {error}",
    "testFailed": "测试生成失败: {error}",
    "refactorComplete": "重构完成: {suggestions}"
  },
  "output": {
    "explanation": "代码解释",
    "debugAnalysis": "调试分析",
    "aiGenerated": "AI生成",
    "agentSuggestion": "编码助手建议"
  }
}

ja.json

{
  "extension": {
    "activated": "コーディングエージェント拡張が有効になりました",
    "ready": "エージェント準備完了",
    "completing": "補完中...",
    "generating": "生成中...",
    "explaining": "説明中...",
    "refactoring": "リファクタリング中...",
    "debugging": "デバッグ中...",
    "generatingTests": "テスト生成中...",
    "error": "エラー",
    "disconnected": "切断",
    "connected": "コーディングエージェントバックエンドに接続しました",
    "cannotConnect": "コーディングエージェントバックエンドに接続できません。サービスが起動していることを確認してください"
  },
  "commands": {
    "complete": "エージェント: コード補完",
    "generate": "エージェント: コード生成",
    "explain": "エージェント: コード説明",
    "refactor": "エージェント: コードリファクタリング",
    "debug": "エージェント: コードデバッグ",
    "test": "エージェント: テスト生成"
  },
  "prompts": {
    "generateDescription": "生成したいコードを説明してください",
    "generatePlaceholder": "例:REST APIエンドポイントを作成",
    "selectCode": "最初にコードを選択してください",
    "completeFailed": "補完失敗: {error}",
    "generateFailed": "生成失敗: {error}",
    "explainFailed": "説明失敗: {error}",
    "refactorFailed": "リファクタリング失敗: {error}",
    "debugFailed": "デバッグ失敗: {error}",
    "testFailed": "テスト生成失敗: {error}",
    "refactorComplete": "リファクタリング完了: {suggestions}"
  },
  "output": {
    "explanation": "コード説明",
    "debugAnalysis": "デバッグ分析",
    "aiGenerated": "AI生成",
    "agentSuggestion": "コーディングエージェントの提案"
  }
}

4. 扩展主文件 (extension.js)

// vscode-extension/extension.js
const vscode = require('vscode');
const axios = require('axios');
const { localize, i18n } = require('./i18n');

// 配置
const API_BASE_URL = 'http://localhost:8000/api';
let statusBarItem;

function activate(context) {
    console.log(localize('extension.activated'));
    
    // 创建状态栏项
    statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
    statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
    statusBarItem.show();
    
    // 获取当前语言设置
    const currentLang = i18n.getCurrentLanguage();
    
    // 创建axios实例,自动添加语言头
    const apiClient = axios.create({
        baseURL: API_BASE_URL,
        headers: {
            'Accept-Language': currentLang
        }
    });
    
    // 检查后端连接
    checkBackendConnection(apiClient);
    
    // 注册命令:代码补全
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.complete', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) return;
        
        const document = editor.document;
        const position = editor.selection.active;
        const code = document.getText();
        const cursorOffset = document.offsetAt(position);
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.completing')}`;
            
            const response = await apiClient.post('/code', {
                action: 'complete',
                code,
                cursor_position: cursorOffset,
                language: document.languageId,
                max_tokens: 256
            });
            
            // 插入补全的代码
            await editor.edit(editBuilder => {
                editBuilder.insert(position, response.data.result);
            });
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.completeFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 注册命令:生成代码
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.generate', async () => {
        const instruction = await vscode.window.showInputBox({
            prompt: localize('prompts.generateDescription'),
            placeHolder: localize('prompts.generatePlaceholder')
        });
        
        if (!instruction) return;
        
        const editor = vscode.window.activeTextEditor;
        const language = editor ? editor.document.languageId : 'python';
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.generating')}`;
            
            const response = await apiClient.post('/code', {
                action: 'generate',
                instruction,
                language,
                max_tokens: 512
            });
            
            if (editor) {
                await editor.edit(editBuilder => {
                    editBuilder.insert(editor.selection.active, response.data.result);
                });
            } else {
                const doc = await vscode.workspace.openTextDocument({ content: response.data.result, language });
                await vscode.window.showTextDocument(doc);
            }
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.generateFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 注册命令:解释代码
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.explain', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) return;
        
        const selection = editor.selection;
        const code = editor.document.getText(selection);
        
        if (!code) {
            vscode.window.showWarningMessage(localize('prompts.selectCode'));
            return;
        }
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.explaining')}`;
            
            const response = await apiClient.post('/code', {
                action: 'explain',
                code,
                language: editor.document.languageId
            });
            
            const outputChannel = vscode.window.createOutputChannel(localize('output.explanation'));
            outputChannel.appendLine(`=== ${localize('output.explanation')} ===\n`);
            outputChannel.appendLine(response.data.result);
            outputChannel.show();
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.explainFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 注册命令:重构代码
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.refactor', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) return;
        
        const selection = editor.selection;
        const code = editor.document.getText(selection);
        
        if (!code) {
            vscode.window.showWarningMessage(localize('prompts.selectCode'));
            return;
        }
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.refactoring')}`;
            
            const response = await apiClient.post('/code', {
                action: 'refactor',
                code,
                language: editor.document.languageId
            });
            
            await editor.edit(editBuilder => {
                editBuilder.replace(selection, response.data.result);
            });
            
            if (response.data.suggestions && response.data.suggestions.length > 0) {
                vscode.window.showInformationMessage(localize('prompts.refactorComplete', { suggestions: response.data.suggestions.join(', ') }));
            }
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.refactorFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 注册命令:调试代码
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.debug', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) return;
        
        const code = editor.document.getText();
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.debugging')}`;
            
            const response = await apiClient.post('/code', {
                action: 'debug',
                code,
                language: editor.document.languageId
            });
            
            const outputChannel = vscode.window.createOutputChannel(localize('output.debugAnalysis'));
            outputChannel.appendLine(`=== ${localize('output.debugAnalysis')} ===\n`);
            outputChannel.appendLine(response.data.result);
            outputChannel.show();
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.debugFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 注册命令:生成测试
    context.subscriptions.push(vscode.commands.registerCommand('codingAgent.test', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) return;
        
        const selection = editor.selection;
        const code = selection.isEmpty ? editor.document.getText() : editor.document.getText(selection);
        
        try {
            statusBarItem.text = `$(sync~spin) ${localize('extension.generatingTests')}`;
            
            const response = await apiClient.post('/code', {
                action: 'test',
                code,
                language: editor.document.languageId
            });
            
            const testDoc = await vscode.workspace.openTextDocument({
                content: response.data.result,
                language: editor.document.languageId
            });
            await vscode.window.showTextDocument(testDoc, vscode.ViewColumn.Beside);
            
            statusBarItem.text = `$(sparkle) ${localize('extension.ready')}`;
        } catch (error) {
            vscode.window.showErrorMessage(localize('prompts.testFailed', { error: error.message }));
            statusBarItem.text = `$(error) ${localize('extension.error')}`;
        }
    }));
    
    // 自动补全提供者(可选:注册为Inline Completion Provider)
    context.subscriptions.push(vscode.languages.registerInlineCompletionItemProvider(
        { scheme: 'file', language: '*' }, 
        {
            async provideInlineCompletionItems(document, position, context, token) {
                const codeBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
                const codeAfter = document.getText(new vscode.Range(position, document.lineAt(document.lineCount - 1).range.end));
                
                try {
                    const response = await apiClient.post('/code', {
                        action: 'complete',
                        code: codeBefore + codeAfter,
                        cursor_position: document.offsetAt(position),
                        language: document.languageId,
                        max_tokens: 100
                    });
                    
                    return [new vscode.InlineCompletionItem(response.data.result)];
                } catch (error) {
                    console.error('Inline completion failed:', error);
                    return [];
                }
            }
        }
    ));
    
    // 监听语言变化(如果VSCode语言改变,重新加载)
    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(event => {
        if (event.affectsConfiguration('general.language')) {
            i18n.loadTranslations();
        }
    }));
}

// 检查后端连接函数
async function checkBackendConnection(apiClient) {
    try {
        const response = await apiClient.get('/health');
        if (response.data.status === 'healthy') {
            statusBarItem.text = `$(sparkle) ${localize('extension.connected')}`;
        } else {
            statusBarItem.text = `$(alert) ${localize('extension.cannotConnect')}`;
        }
    } catch (error) {
        statusBarItem.text = `$(alert) ${localize('extension.cannotConnect')}`;
    }
}

function deactivate() {
    if (statusBarItem) {
        statusBarItem.dispose();
    }
}

module.exports = {
    activate,
    deactivate
};

5. 扩展配置 (package.json)

{
  "name": "coding-agent",
  "displayName": "Coding Agent",
  "description": "Local AI-powered coding assistant with multi-language support for VS Code",
  "version": "1.0.0",
  "engines": {
    "vscode": "^1.80.0"
  },
  "categories": [
    "Programming Languages",
    "Machine Learning"
  ],
  "activationEvents": [
    "onLanguage:python",
    "onLanguage:javascript",
    "onLanguage:java",
    "onCommand:codingAgent.complete",
    "onCommand:codingAgent.generate",
    "onCommand:codingAgent.explain",
    "onCommand:codingAgent.refactor",
    "onCommand:codingAgent.debug",
    "onCommand:codingAgent.test"
  ],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "codingAgent.complete",
        "title": "%commands.complete%"
      },
      {
        "command": "codingAgent.generate",
        "title": "%commands.generate%"
      },
      {
        "command": "codingAgent.explain",
        "title": "%commands.explain%"
      },
      {
        "command": "codingAgent.refactor",
        "title": "%commands.refactor%"
      },
      {
        "command": "codingAgent.debug",
        "title": "%commands.debug%"
      },
      {
        "command": "codingAgent.test",
        "title": "%commands.test%"
      }
    ],
    "keybindings": [
      {
        "command": "codingAgent.complete",
        "key": "ctrl+shift+space",
        "mac": "cmd+shift+space"
      },
      {
        "command": "codingAgent.generate",
        "key": "ctrl+shift+g",
        "mac": "cmd+shift+g"
      },
      {
        "command": "codingAgent.explain",
        "key": "ctrl+shift+e",
        "mac": "cmd+shift+e"
      },
      {
        "command": "codingAgent.refactor",
        "key": "ctrl+shift+r",
        "mac": "cmd+shift+r"
      },
      {
        "command": "codingAgent.debug",
        "key": "ctrl+shift+d",
        "mac": "cmd+shift+d"
      },
      {
        "command": "codingAgent.test",
        "key": "ctrl+shift+t",
        "mac": "cmd+shift+t"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "codingAgent.complete",
          "group": "codingAgent@1"
        },
        {
          "command": "codingAgent.explain",
          "group": "codingAgent@2"
        },
        {
          "command": "codingAgent.refactor",
          "group": "codingAgent@3"
        },
        {
          "command": "codingAgent.test",
          "group": "codingAgent@4"
        }
      ]
    }
  },
  "scripts": {
    "lint": "eslint .",
    "pretest": "npm run lint",
    "test": "node ./test/runTest.js"
  },
  "dependencies": {
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "@types/vscode": "^1.80.0",
    "@types/mocha": "^10.0.0",
    "@types/node": "16.x",
    "eslint": "^8.0.0",
    "@vscode/test-electron": "^2.0.0"
  }
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐