自定义编辑器
自定义编辑器(Custom Editor)让插件为特定文件类型提供专属编辑界面:图片预览、流程图设计器、Markdown 所见即所得……它把「打开文件」变成「打开你的应用」。
自定义编辑器是什么
| 特性 | 说明 |
|---|---|
| 关联文件类型 | 通过 viewType 与文件扩展名关联 |
| 两种形式 | 文本编辑器 + 自定义 Webview 编辑器 |
| 数据来源 | 读取文件内容,显示在自定义 UI |
| 保存机制 | 手动/自动同步回文件 |
适合场景:二进制/富格式文件、可视化编辑、预览增强。
注册 contributes.customEditors
json
{
"contributes": {
"customEditors": [
{
"viewType": "myExt.markdownPreview", // 编辑器类型 ID
"displayName": "Markdown 增强预览",
"selector": [
{ "filenamePattern": "*.md" } // 关联文件
],
"priority": "default" // 优先级
}
]
}
}selector 文件匹配
| 写法 | 匹配 |
|---|---|
"*.md" | 所有 .md 文件 |
"**/docs/*.md" | docs 目录下的 .md |
"*.{md,markdown}" | 多扩展名 |
priority 优先级
| 值 | 行为 |
|---|---|
default | 作为默认编辑器之一 |
option | 仅出现在「打开方式」列表 |
builtin | 覆盖内置编辑器(谨慎) |
实现 CustomTextEditorProvider
文本型自定义编辑器基于文档内容渲染:
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.window.registerCustomEditorProvider(
'myExt.markdownPreview',
new MarkdownEditorProvider(context.extensionUri),
{
webviewOptions: { retainContextWhenHidden: true },
supportsMultipleEditorsPerDocument: false
}
)
);
}
class MarkdownEditorProvider
implements vscode.CustomTextEditorProvider {
constructor(private readonly extensionUri: vscode.Uri) {}
async resolveCustomTextEditor(
document: vscode.TextDocument,
webviewPanel: vscode.WebviewPanel,
token: vscode.CancellationToken
): Promise<void> {
// 1. 设置 Webview
webviewPanel.webview.options = {
enableScripts: true,
localResourceRoots: [this.extensionUri]
};
// 2. 渲染初始内容
webviewPanel.webview.html = this.getHtml(document.getText());
// 3. 监听文档变更,更新 UI
const changeSubscription = vscode.workspace.onDidChangeTextDocument(
(event) => {
if (event.document.uri.toString() === document.uri.toString()) {
webviewPanel.webview.postMessage({
type: 'update',
content: document.getText()
});
}
}
);
webviewPanel.onDidDispose(() => changeSubscription.dispose());
}
private getHtml(content: string): string {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: var(--vscode-font-family); padding: 20px; }
pre { background: var(--vscode-textBlockQuote-background); padding: 12px; }
</style>
</head>
<body>
<h3>Markdown 内容</h3>
<pre id="content"></pre>
<script>
const vscode = acquireVsCodeApi();
document.getElementById('content').textContent =
${JSON.stringify(content)};
window.addEventListener('message', (event) => {
if (event.data.type === 'update') {
document.getElementById('content').textContent =
event.data.content;
}
});
</script>
</body>
</html>`;
}
}保存内容:回到文档
自定义编辑器把修改写回 TextDocument,实现「保存」:
typescript
// Webview 中发送保存请求
vscode.postMessage({ type: 'save', text: editorContent });typescript
// 插件侧监听
webviewPanel.webview.onDidReceiveMessage(
async (message) => {
if (message.type === 'save') {
// 获取文档全文 Range
const fullRange = new vscode.Range(
document.positionAt(0),
document.positionAt(document.getText().length)
);
// 写入文档(触发 onDidChangeTextDocument 与保存)
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, fullRange, message.text);
await vscode.workspace.applyEdit(edit);
}
},
undefined,
context.subscriptions
);保存方式对比
| 方式 | 说明 | 适用 |
|---|---|---|
| WorkspaceEdit 写回 | 走正常文档通道 | 文本型编辑器 |
| document.save() | 显式保存 | 直接落盘 |
| 自动保存 | 用户设置触发 | 编辑型 |
撤销/恢复
写回文档后,撤销功能由 VS Code 编辑历史自动支持:
typescript
// 需要撤销支持时,监听并记录
const undoStack: string[] = [];
const redoStack: string[] = [];
webviewPanel.webview.onDidReceiveMessage((message) => {
if (message.type === 'undo') {
const prev = undoStack.pop();
if (prev) {
redoStack.push(message.current);
applyContent(prev);
}
}
});CustomReadonlyEditorProvider
只读编辑器(如预览、查看器)更简单:
typescript
class PreviewProvider implements vscode.CustomReadonlyEditorProvider {
resolveCustomEditor(
document: vscode.CustomDocument,
webviewPanel: vscode.WebviewPanel
): void {
// 只读渲染,无需处理保存
webviewPanel.webview.html = this.getHtml(document);
}
}完整示例:JSON 格式化编辑器
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.window.registerCustomEditorProvider(
'myExt.jsonFormatter',
new JsonEditorProvider(context.extensionUri),
{ webviewOptions: { retainContextWhenHidden: true } }
)
);
}
class JsonEditorProvider implements vscode.CustomTextEditorProvider {
constructor(private readonly extensionUri: vscode.Uri) {}
async resolveCustomTextEditor(
document: vscode.TextDocument,
panel: vscode.WebviewPanel
): Promise<void> {
panel.webview.options = { enableScripts: true };
const render = (text: string) => {
panel.webview.html = this.getHtml(text);
};
// 初始渲染
render(document.getText());
// 文档变更实时渲染
const sub = vscode.workspace.onDidChangeTextDocument((event) => {
if (event.document.uri.toString() === document.uri.toString()) {
render(document.getText());
}
});
panel.onDidDispose(() => sub.dispose());
}
private getHtml(jsonText: string): string {
let formatted: string;
try {
formatted = JSON.stringify(JSON.parse(jsonText), null, 2);
} catch (error) {
formatted = jsonText + '\n\n// JSON 解析错误';
}
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: monospace; padding: 20px; }
pre { white-space: pre-wrap; }
</style>
</head>
<body>
<h3>JSON 格式化视图</h3>
<pre>${formatted}</pre>
</body>
</html>`;
}
}常见问题
| 问题 | 处理 |
|---|---|
| 文件打开用默认编辑器 | 检查 priority 与 selector |
| 内容不同步 | 监听 onDidChangeTextDocument |
| 保存无效 | 用 WorkspaceEdit 写回文档 |
| 撤销不工作 | 编辑必须写回文档而非仅更新 UI |
自定义编辑器把插件能力直接接入文件打开流程,是构建「专属文件格式工具」的标准方案。