Skip to content

代码操作 API

代码操作 API 提供了在 VS Code 中实现代码重构、快速修复和其他代码转换功能的能力。代码操作是用户可以在编辑器中触发的智能操作。

概述

代码操作是 VS Code 语言功能的重要组成部分,允许扩展程序:

  • 提供快速修复建议
  • 实现代码重构功能
  • 执行代码转换操作
  • 响应诊断问题
  • 提供源代码操作

主要接口

CodeActionProvider

typescript
interface CodeActionProvider {
    /**
     * 提供代码操作
     */
    provideCodeActions(
        document: TextDocument,
        range: Range | Selection,
        context: CodeActionContext,
        token: CancellationToken
    ): ProviderResult<(CodeAction | Command)[]>;

    /**
     * 解析代码操作(可选)
     */
    resolveCodeAction?(
        codeAction: CodeAction,
        token: CancellationToken
    ): ProviderResult<CodeAction>;
}

CodeAction

typescript
class CodeAction {
    /**
     * 代码操作的标题
     */
    title: string;

    /**
     * 要执行的编辑操作
     */
    edit?: WorkspaceEdit;

    /**
     * 诊断信息
     */
    diagnostics?: Diagnostic[];

    /**
     * 代码操作的种类
     */
    kind?: CodeActionKind;

    /**
     * 是否为首选操作
     */
    isPreferred?: boolean;

    /**
     * 是否禁用
     */
    disabled?: {
        reason: string;
    };

    /**
     * 要执行的命令
     */
    command?: Command;

    constructor(title: string, kind?: CodeActionKind);
}

CodeActionKind

typescript
class CodeActionKind {
    /**
     * 空的代码操作种类
     */
    static readonly Empty: CodeActionKind;

    /**
     * 快速修复
     */
    static readonly QuickFix: CodeActionKind;

    /**
     * 重构
     */
    static readonly Refactor: CodeActionKind;

    /**
     * 提取重构
     */
    static readonly RefactorExtract: CodeActionKind;

    /**
     * 内联重构
     */
    static readonly RefactorInline: CodeActionKind;

    /**
     * 重写重构
     */
    static readonly RefactorRewrite: CodeActionKind;

    /**
     * 源代码操作
     */
    static readonly Source: CodeActionKind;

    /**
     * 组织导入
     */
    static readonly SourceOrganizeImports: CodeActionKind;

    /**
     * 修复所有问题
     */
    static readonly SourceFixAll: CodeActionKind;
}

注册代码操作提供者

基本注册

typescript
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    // 注册代码操作提供者
    const provider = new MyCodeActionProvider();
    
    const disposable = vscode.languages.registerCodeActionsProvider(
        { scheme: 'file', language: 'typescript' },
        provider,
        {
            providedCodeActionKinds: [
                vscode.CodeActionKind.QuickFix,
                vscode.CodeActionKind.Refactor
            ]
        }
    );

    context.subscriptions.push(disposable);
}

多语言支持

typescript
// 支持多种语言
const selector: vscode.DocumentSelector = [
    { scheme: 'file', language: 'typescript' },
    { scheme: 'file', language: 'javascript' },
    { scheme: 'file', language: 'typescriptreact' },
    { scheme: 'file', language: 'javascriptreact' }
];

const provider = new UniversalCodeActionProvider();
const disposable = vscode.languages.registerCodeActionsProvider(
    selector,
    provider
);

实现代码操作提供者

快速修复提供者

typescript
class QuickFixProvider implements vscode.CodeActionProvider {
    provideCodeActions(
        document: vscode.TextDocument,
        range: vscode.Range | vscode.Selection,
        context: vscode.CodeActionContext,
        token: vscode.CancellationToken
    ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
        const actions: vscode.CodeAction[] = [];

        // 处理诊断相关的快速修复
        for (const diagnostic of context.diagnostics) {
            if (this.canFixDiagnostic(diagnostic)) {
                const action = this.createQuickFix(document, range, diagnostic);
                if (action) {
                    actions.push(action);
                }
            }
        }

        // 添加通用快速修复
        const generalFixes = this.provideGeneralQuickFixes(document, range);
        actions.push(...generalFixes);

        return actions;
    }

    private canFixDiagnostic(diagnostic: vscode.Diagnostic): boolean {
        // 检查是否可以修复此诊断
        return diagnostic.source === 'my-extension' && 
               diagnostic.code === 'unused-variable';
    }

    private createQuickFix(
        document: vscode.TextDocument,
        range: vscode.Range,
        diagnostic: vscode.Diagnostic
    ): vscode.CodeAction | undefined {
        if (diagnostic.code === 'unused-variable') {
            return this.createRemoveUnusedVariableFix(document, diagnostic);
        }
        return undefined;
    }

    private createRemoveUnusedVariableFix(
        document: vscode.TextDocument,
        diagnostic: vscode.Diagnostic
    ): vscode.CodeAction {
        const action = new vscode.CodeAction(
            '移除未使用的变量',
            vscode.CodeActionKind.QuickFix
        );

        // 创建编辑操作
        const edit = new vscode.WorkspaceEdit();
        edit.delete(document.uri, diagnostic.range);

        action.edit = edit;
        action.diagnostics = [diagnostic];
        action.isPreferred = true;

        return action;
    }

    private provideGeneralQuickFixes(
        document: vscode.TextDocument,
        range: vscode.Range
    ): vscode.CodeAction[] {
        const actions: vscode.CodeAction[] = [];

        // 添加导入修复
        const importFix = this.createAddImportFix(document, range);
        if (importFix) {
            actions.push(importFix);
        }

        return actions;
    }

    private createAddImportFix(
        document: vscode.TextDocument,
        range: vscode.Range
    ): vscode.CodeAction | undefined {
        const line = document.lineAt(range.start.line);
        const text = line.text;

        // 检查是否需要添加导入
        const match = text.match(/(\w+)\s+is not defined/);
        if (match) {
            const variableName = match[1];
            return this.createImportAction(document, variableName);
        }

        return undefined;
    }

    private createImportAction(
        document: vscode.TextDocument,
        variableName: string
    ): vscode.CodeAction {
        const action = new vscode.CodeAction(
            `添加导入: ${variableName}`,
            vscode.CodeActionKind.QuickFix
        );

        const edit = new vscode.WorkspaceEdit();
        const importStatement = `import { ${variableName} } from './${variableName}';\n`;
        
        edit.insert(document.uri, new vscode.Position(0, 0), importStatement);
        action.edit = edit;

        return action;
    }
}

重构提供者

typescript
class RefactorProvider implements vscode.CodeActionProvider {
    provideCodeActions(
        document: vscode.TextDocument,
        range: vscode.Range | vscode.Selection,
        context: vscode.CodeActionContext,
        token: vscode.CancellationToken
    ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
        const actions: vscode.CodeAction[] = [];

        // 只在选择了文本时提供重构操作
        if (range instanceof vscode.Selection && !range.isEmpty) {
            // 提取方法
            const extractMethod = this.createExtractMethodAction(document, range);
            if (extractMethod) {
                actions.push(extractMethod);
            }

            // 提取变量
            const extractVariable = this.createExtractVariableAction(document, range);
            if (extractVariable) {
                actions.push(extractVariable);
            }
        }

        // 重命名符号
        const renameSymbol = this.createRenameSymbolAction(document, range);
        if (renameSymbol) {
            actions.push(renameSymbol);
        }

        return actions;
    }

    private createExtractMethodAction(
        document: vscode.TextDocument,
        selection: vscode.Selection
    ): vscode.CodeAction | undefined {
        const selectedText = document.getText(selection);
        
        // 检查选择的文本是否适合提取为方法
        if (this.canExtractAsMethod(selectedText)) {
            const action = new vscode.CodeAction(
                '提取方法',
                vscode.CodeActionKind.RefactorExtract
            );

            // 使用命令而不是直接编辑,因为重构可能需要用户输入
            action.command = {
                command: 'myExtension.extractMethod',
                title: '提取方法',
                arguments: [document.uri, selection]
            };

            return action;
        }

        return undefined;
    }

    private createExtractVariableAction(
        document: vscode.TextDocument,
        selection: vscode.Selection
    ): vscode.CodeAction | undefined {
        const selectedText = document.getText(selection);
        
        if (this.canExtractAsVariable(selectedText)) {
            const action = new vscode.CodeAction(
                '提取变量',
                vscode.CodeActionKind.RefactorExtract
            );

            action.command = {
                command: 'myExtension.extractVariable',
                title: '提取变量',
                arguments: [document.uri, selection]
            };

            return action;
        }

        return undefined;
    }

    private createRenameSymbolAction(
        document: vscode.TextDocument,
        range: vscode.Range
    ): vscode.CodeAction | undefined {
        // 检查当前位置是否有符号
        const wordRange = document.getWordRangeAtPosition(range.start);
        if (wordRange) {
            const action = new vscode.CodeAction(
                '重命名符号',
                vscode.CodeActionKind.Refactor
            );

            action.command = {
                command: 'editor.action.rename',
                title: '重命名符号'
            };

            return action;
        }

        return undefined;
    }

    private canExtractAsMethod(text: string): boolean {
        // 简单检查:包含多行且不是单个表达式
        return text.includes('\n') && text.trim().length > 10;
    }

    private canExtractAsVariable(text: string): boolean {
        // 简单检查:单行表达式
        return !text.includes('\n') && text.trim().length > 5;
    }
}

源代码操作

组织导入

typescript
class SourceActionProvider implements vscode.CodeActionProvider {
    provideCodeActions(
        document: vscode.TextDocument,
        range: vscode.Range | vscode.Selection,
        context: vscode.CodeActionContext,
        token: vscode.CancellationToken
    ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
        const actions: vscode.CodeAction[] = [];

        // 只在请求源代码操作时提供
        if (context.only && context.only.contains(vscode.CodeActionKind.Source)) {
            // 组织导入
            const organizeImports = this.createOrganizeImportsAction(document);
            actions.push(organizeImports);

            // 修复所有问题
            const fixAll = this.createFixAllAction(document);
            actions.push(fixAll);

            // 移除未使用的导入
            const removeUnused = this.createRemoveUnusedImportsAction(document);
            actions.push(removeUnused);
        }

        return actions;
    }

    private createOrganizeImportsAction(document: vscode.TextDocument): vscode.CodeAction {
        const action = new vscode.CodeAction(
            '组织导入',
            vscode.CodeActionKind.SourceOrganizeImports
        );

        const edit = new vscode.WorkspaceEdit();
        const organizedImports = this.organizeImports(document);
        
        if (organizedImports) {
            edit.replace(document.uri, organizedImports.range, organizedImports.text);
            action.edit = edit;
        }

        return action;
    }

    private createFixAllAction(document: vscode.TextDocument): vscode.CodeAction {
        const action = new vscode.CodeAction(
            '修复所有问题',
            vscode.CodeActionKind.SourceFixAll
        );

        action.command = {
            command: 'myExtension.fixAll',
            title: '修复所有问题',
            arguments: [document.uri]
        };

        return action;
    }

    private createRemoveUnusedImportsAction(document: vscode.TextDocument): vscode.CodeAction {
        const action = new vscode.CodeAction(
            '移除未使用的导入',
            vscode.CodeActionKind.Source
        );

        const edit = new vscode.WorkspaceEdit();
        const unusedImports = this.findUnusedImports(document);
        
        unusedImports.forEach(range => {
            edit.delete(document.uri, range);
        });

        action.edit = edit;
        return action;
    }

    private organizeImports(document: vscode.TextDocument): { range: vscode.Range; text: string } | undefined {
        // 实现导入组织逻辑
        const imports = this.extractImports(document);
        if (imports.length === 0) return undefined;

        const sortedImports = this.sortImports(imports);
        const organizedText = sortedImports.join('\n') + '\n';

        return {
            range: new vscode.Range(0, 0, imports.length, 0),
            text: organizedText
        };
    }

    private extractImports(document: vscode.TextDocument): string[] {
        const imports: string[] = [];
        
        for (let i = 0; i < document.lineCount; i++) {
            const line = document.lineAt(i);
            if (line.text.trim().startsWith('import ')) {
                imports.push(line.text);
            } else if (line.text.trim() && !line.text.trim().startsWith('//')) {
                // 遇到非导入、非注释的代码行,停止
                break;
            }
        }

        return imports;
    }

    private sortImports(imports: string[]): string[] {
        return imports.sort((a, b) => {
            // 简单的排序逻辑:按模块名排序
            const moduleA = this.extractModuleName(a);
            const moduleB = this.extractModuleName(b);
            return moduleA.localeCompare(moduleB);
        });
    }

    private extractModuleName(importStatement: string): string {
        const match = importStatement.match(/from\s+['"]([^'"]+)['"]/);
        return match ? match[1] : '';
    }

    private findUnusedImports(document: vscode.TextDocument): vscode.Range[] {
        // 实现查找未使用导入的逻辑
        const unusedRanges: vscode.Range[] = [];
        // 这里应该实现实际的未使用导入检测逻辑
        return unusedRanges;
    }
}

实用示例

智能代码操作管理器

typescript
class SmartCodeActionManager {
    private providers = new Map<string, vscode.CodeActionProvider>();

    registerProvider(
        language: string,
        provider: vscode.CodeActionProvider,
        kinds?: vscode.CodeActionKind[]
    ): vscode.Disposable {
        this.providers.set(language, provider);

        return vscode.languages.registerCodeActionsProvider(
            { scheme: 'file', language },
            provider,
            { providedCodeActionKinds: kinds }
        );
    }

    async executeCodeAction(
        document: vscode.TextDocument,
        range: vscode.Range,
        actionTitle: string
    ): Promise<boolean> {
        const provider = this.providers.get(document.languageId);
        if (!provider) return false;

        const context: vscode.CodeActionContext = {
            diagnostics: vscode.languages.getDiagnostics(document.uri),
            only: undefined,
            triggerKind: vscode.CodeActionTriggerKind.Invoke
        };

        const actions = await provider.provideCodeActions(
            document,
            range,
            context,
            new vscode.CancellationTokenSource().token
        );

        if (!actions) return false;

        const targetAction = actions.find(action => 
            action instanceof vscode.CodeAction && action.title === actionTitle
        );

        if (targetAction instanceof vscode.CodeAction) {
            if (targetAction.edit) {
                await vscode.workspace.applyEdit(targetAction.edit);
                return true;
            } else if (targetAction.command) {
                await vscode.commands.executeCommand(
                    targetAction.command.command,
                    ...(targetAction.command.arguments || [])
                );
                return true;
            }
        }

        return false;
    }
}

批量代码操作执行器

typescript
class BatchCodeActionExecutor {
    async executeActionsOnWorkspace(
        actionKind: vscode.CodeActionKind,
        filePattern: string = '**/*.{ts,js}'
    ): Promise<void> {
        const files = await vscode.workspace.findFiles(filePattern);
        
        for (const file of files) {
            await this.executeActionsOnFile(file, actionKind);
        }
    }

    private async executeActionsOnFile(
        uri: vscode.Uri,
        actionKind: vscode.CodeActionKind
    ): Promise<void> {
        const document = await vscode.workspace.openTextDocument(uri);
        const fullRange = new vscode.Range(
            0, 0,
            document.lineCount - 1,
            document.lineAt(document.lineCount - 1).text.length
        );

        const context: vscode.CodeActionContext = {
            diagnostics: vscode.languages.getDiagnostics(uri),
            only: actionKind,
            triggerKind: vscode.CodeActionTriggerKind.Invoke
        };

        // 获取所有可用的代码操作
        const actions = await vscode.commands.executeCommand<vscode.CodeAction[]>(
            'vscode.executeCodeActionProvider',
            uri,
            fullRange,
            actionKind.value
        );

        if (actions) {
            for (const action of actions) {
                if (action.edit) {
                    await vscode.workspace.applyEdit(action.edit);
                } else if (action.command) {
                    await vscode.commands.executeCommand(
                        action.command.command,
                        ...(action.command.arguments || [])
                    );
                }
            }
        }
    }
}

最佳实践

1. 性能优化

typescript
// 缓存昂贵的计算结果
class OptimizedCodeActionProvider implements vscode.CodeActionProvider {
    private cache = new Map<string, vscode.CodeAction[]>();

    provideCodeActions(
        document: vscode.TextDocument,
        range: vscode.Range,
        context: vscode.CodeActionContext
    ): vscode.ProviderResult<vscode.CodeAction[]> {
        const cacheKey = this.getCacheKey(document, range, context);
        
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }

        const actions = this.computeCodeActions(document, range, context);
        this.cache.set(cacheKey, actions);
        
        return actions;
    }

    private getCacheKey(
        document: vscode.TextDocument,
        range: vscode.Range,
        context: vscode.CodeActionContext
    ): string {
        return `${document.uri.toString()}-${document.version}-${range.start.line}-${range.start.character}`;
    }
}

2. 用户体验

typescript
// 提供有意义的操作标题和描述
function createUserFriendlyAction(title: string, description?: string): vscode.CodeAction {
    const action = new vscode.CodeAction(title, vscode.CodeActionKind.QuickFix);
    
    // 添加详细描述
    if (description) {
        action.title = `${title} - ${description}`;
    }
    
    // 标记为首选操作
    action.isPreferred = true;
    
    return action;
}

3. 错误处理

typescript
// 安全的代码操作提供
async function safeProvideCodeActions(
    provider: vscode.CodeActionProvider,
    document: vscode.TextDocument,
    range: vscode.Range,
    context: vscode.CodeActionContext
): Promise<vscode.CodeAction[]> {
    try {
        const result = await provider.provideCodeActions(document, range, context, new vscode.CancellationTokenSource().token);
        return Array.isArray(result) ? result.filter(action => action instanceof vscode.CodeAction) : [];
    } catch (error) {
        console.error('代码操作提供失败:', error);
        return [];
    }
}

相关 API

您的终极 AI 驱动 IDE 学习指南