代码格式化与操作
格式化(Format)与代码操作(Code Actions)是编辑体验的「自动助手」:一键整理代码风格、快速应用重构。本章实现这两个能力。
文档格式化 registerDocumentFormattingEditProvider
registerDocumentFormattingEditProvider 注册整文档格式化:
typescript
import * as vscode from 'vscode';
vscode.languages.registerDocumentFormattingEditProvider(
'plaintext',
{
provideDocumentFormattingEdits(
document: vscode.TextDocument,
options: vscode.FormattingOptions,
token: vscode.CancellationToken
): vscode.TextEdit[] {
// 返回文本编辑数组
return formatText(document);
}
}
);FormattingOptions 配置
typescript
interface FormattingOptions {
tabSize: number; // Tab 宽度
insertSpaces: boolean; // 是否空格缩进
// 语言特定选项
}DocumentRangeFormattingEditProvider 选区格式化
只格式化选中的范围:
typescript
vscode.languages.registerDocumentRangeFormattingEditProvider(
'plaintext',
{
provideDocumentRangeFormattingEdits(
document,
range,
options,
token
): vscode.TextEdit[] {
// 只处理选区内容
const selectedText = document.getText(range);
const formatted = formatContent(selectedText);
return [vscode.TextEdit.replace(range, formatted)];
}
}
);格式化实现:文本编辑
typescript
function formatText(
document: vscode.TextDocument
): vscode.TextEdit[] {
const edits: vscode.TextEdit[] = [];
const options = getFormatOptions();
// 遍历每行
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
const editsForLine: vscode.TextEdit[] = [];
// 1. 去除行尾空格
const trailingMatch = text.match(/\s+$/);
if (trailingMatch) {
editsForLine.push(
vscode.TextEdit.delete(
new vscode.Range(line, trailingMatch.index, line, text.length)
)
);
}
// 2. 统一缩进
const leadingMatch = text.match(/^\s+/);
if (leadingMatch) {
const indent = normalizeIndent(leadingMatch[0], options);
if (indent !== leadingMatch[0]) {
editsForLine.push(
vscode.TextEdit.replace(
new vscode.Range(line, 0, line, leadingMatch[0].length),
indent
)
);
}
}
// 3. 运算符两侧空格
const operatorSpaced = text
.replace(/(\w)\s*=\s*(\w)/g, '$1 = $2')
.replace(/(\w)\s*\+\s*(\w)/g, '$1 + $2');
if (operatorSpaced !== text) {
editsForLine.push(
vscode.TextEdit.replace(
new vscode.Range(line, 0, line, text.length),
operatorSpaced
)
);
}
edits.push(...editsForLine);
}
return edits;
}
function normalizeIndent(
indent: string,
options: vscode.FormattingOptions
): string {
// Tab 转空格
if (options.insertSpaces) {
return indent.replace(/\t/g, ' '.repeat(options.tabSize));
}
// 空格转 Tab
return indent.replace(/ {2}/g, '\t');
}registerCodeActionsProvider 代码操作
代码操作提供重构、修复、组织 import 等能力:
typescript
import * as vscode from 'vscode';
class RefactorProvider implements vscode.CodeActionProvider {
// 声明提供的操作种类
static readonly providedCodeActionKinds = [
vscode.CodeActionKind.RefactorExtract
];
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): vscode.CodeAction[] {
const actions: vscode.CodeAction[] = [];
// 提取变量重构
const text = document.getText(range);
if (text && !range.isEmpty) {
const action = new vscode.CodeAction(
`提取为变量 const`,
vscode.CodeActionKind.RefactorExtract
);
// 构造编辑:替换为变量 + 顶部声明
action.edit = new vscode.WorkspaceEdit();
action.edit.replace(document.uri, range, 'extractedValue');
action.edit.insert(
document.uri,
new vscode.Position(0, 0),
`const extractedValue = ${text};\n`
);
actions.push(action);
}
return actions;
}
}
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
'plaintext',
new RefactorProvider(),
{ providedCodeActionKinds: RefactorProvider.providedCodeActionKinds }
)
);
}CodeActionKind 类型
| 类型 | 用途 |
|---|---|
QuickFix | 快速修复 |
Refactor | 重构 |
RefactorExtract | 提取(变量/函数) |
RefactorInline | 内联 |
RefactorRewrite | 重写 |
Source | 源码操作 |
SourceOrganizeImports | 组织 import |
组织 import
typescript
provideCodeActions(document, range, context) {
const action = new vscode.CodeAction(
'组织 import',
vscode.CodeActionKind.SourceOrganizeImports
);
action.edit = new vscode.WorkspaceEdit();
// 重写 import 区域(排序 + 去重)
const importBlock = buildSortedImports(document);
action.edit.replace(
document.uri,
findImportRange(document),
importBlock
);
return [action];
}完整示例:格式化 + 操作组合
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// 1. 文档格式化
context.subscriptions.push(
vscode.languages.registerDocumentFormattingEditProvider(
'plaintext',
{
provideDocumentFormattingEdits(document, options) {
const edits: vscode.TextEdit[] = [];
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
// 行尾空格清理
const trailing = text.match(/\s+$/);
if (trailing) {
edits.push(
vscode.TextEdit.delete(
new vscode.Range(line, trailing.index, line, text.length)
)
);
}
// 行内多余空格压缩
const compressed = text.replace(/ {2,}/g, ' ');
if (compressed !== text) {
edits.push(
vscode.TextEdit.replace(
new vscode.Range(line, 0, line, text.length),
compressed
)
);
}
}
return edits;
}
}
)
);
// 2. 代码操作:添加注释头
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
'plaintext',
{
provideCodeActions(document, range) {
const action = new vscode.CodeAction(
'添加文件头注释',
vscode.CodeActionKind.QuickFix
);
action.edit = new vscode.WorkspaceEdit();
action.edit.insert(
document.uri,
new vscode.Position(0, 0),
`// File: ${document.fileName.split('\\').pop()}\n` +
`// Formatted: ${new Date().toISOString()}\n\n`
);
return [action];
}
}
)
);
// 3. 命令:格式化整个文档
context.subscriptions.push(
vscode.commands.registerCommand('myExt.formatNow', () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
vscode.commands.executeCommand(
'editor.action.formatDocument'
);
})
);
}格式化触发方式
| 方式 | 说明 |
|---|---|
| Shift+Alt+F | 手动格式化文档 |
| 保存时格式化 | 用户设置 formatOnSave |
| 右键菜单 | Format Document |
| 命令调用 | executeCommand |
常见问题
| 问题 | 处理 |
|---|---|
| 格式化无效 | 检查 TextEdit 范围正确 |
| 保存不格式化 | 用户需开启 formatOnSave |
| 选区格式化不触发 | 注册 range 版本 Provider |
| 代码操作不显示 | 检查 kind 与 providedCodeActionKinds |
格式化与代码操作让插件提供「自动整理」能力,配合快捷键与保存时机,大幅提升代码质量一致性。