代码操作 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 [];
}
}