VSCode 源码二次开发:常用机制速记
VSCode 源码二次开发:常用机制速记
做 fork 版 VSCode 源码二次开发时攒下的一些零碎机制笔记。
插件的加载与激活流程
VSCode 插件从市场到运行大致四个阶段:
- 列表生成:向扩展市场服务端 POST 查询(target =
Microsoft.VisualStudio.Code),拿到插件列表渲染 UI。 - 下载安装:从 CDN 下载
.vsix(本质是 zip),解压到用户目录.vscode/extensions,读出package.json。 - 激活:按
package.json的main字段定位入口,用 Noderequire加载,执行导出的activate()。激活时机由activationEvents(如onLanguage:python、onCommand:xxx)控制,实现懒加载。 - 通信:插件跑在独立扩展宿主进程,通过 IPC(Node
net)+ 代理模式与主进程通信。
源码内置命令:主题切换 / 关闭编辑器
# 切换主题
命令 ID: workbench.action.selectTheme
执行函数: setColorTheme()
位置: src/vs/workbench/services/themes/browser/workbenchThemeService.ts
src/vs/workbench/contrib/themes/browser/themes.contribution.ts
# 关闭当前编辑器
命令 ID: workbench.action.closeActiveEditor
执行类: CloseOneEditorAction.run
位置: src/vs/workbench/browser/parts/editor/editorActions.ts
workspaceStorage 的生成规律
StorageMainService.workspaceStorage()(src/vs/platform/storage/electron-main/storageMainService.ts):
- 打开空窗口时按时间戳生成 storage;
- 打开项目文件夹时生成一个 id,删除后再次打开同一文件夹,id 与之前一致(基于工作区路径稳定派生)。
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain {
let workspaceStorage = this.mapWorkspaceToStorage.get(workspace.id);
if (!workspaceStorage) {
workspaceStorage = this.createWorkspaceStorage(workspace);
this.mapWorkspaceToStorage.set(workspace.id, workspaceStorage);
once(workspaceStorage.onDidCloseStorage)(() => {
this.mapWorkspaceToStorage.delete(workspace.id);
});
}
return workspaceStorage;
}
相关服务注册:src/vs/code/electron-main/app.ts、.../sharedProcess/sharedProcessMain.ts、src/vs/workbench/electron-sandbox/shared.desktop.main.ts。
读取用户配置 getConfigurationData
configurationService.getConfigurationData()?.user.contents.<section> 可读到 settings.json 里的用户配置。注意逐层判空(某层不存在时给默认行为):
const workbench = this.configurationService.getConfigurationData()?.user.contents.workbench;
// 某个自定义开关,未显式配置时默认显示
if (workbench?.myFeature?.visible === false) {
hideMyFeature();
} else {
showMyFeature();
}
在主进程里实现一个 WebSocket 客户端(Node net)
源码主进程(非插件沙箱)可以直接用 Node net 起一个原始 socket,手动完成 WebSocket 握手:
const host = '127.0.0.1';
const port = this._port;
const socket = net.createConnection({ host, port }, () => {
// 生成 16 字节随机 nonce 并 base64
const buffer = Buffer.alloc(16);
for (let i = 0; i < 16; i++) {
buffer[i] = Math.round(Math.random() * 256);
}
const nonce = buffer.toString('base64');
// 发送 WebSocket 升级握手头
const headers = [
`GET ws://${/:/.test(host) ? `[${host}]` : host}:${port}/ HTTP/1.1`,
`Connection: Upgrade`,
`Upgrade: websocket`,
`Sec-WebSocket-Key: ${nonce}`
];
socket.write(headers.join('\r\n') + '\r\n\r\n');
// 收到响应头里的 \r\n\r\n 即握手完成
const onData = (data) => {
if (data.toString().indexOf('\r\n\r\n') >= 0) {
socket.off('data', onData);
// 握手成功,后续按 WebSocket 帧处理
}
};
socket.on('data', onData);
});
socket.setNoDelay(true); // 关闭 Nagle 算法,降低小包延迟
拦截 Editor 关闭 / Tab 行为
编辑器 Tab 的关闭按钮、标题渲染相关逻辑集中在:
src/vs/workbench/browser/parts/editor/editorActions.ts
src/vs/workbench/browser/parts/editor/editorGroupView.ts
src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
需要对某类特殊 webview(按 extension.id 或 tab 的 DOM 属性判断)定制 Tab 外观 / 行为时,可在 tabsTitleControl 渲染处加判断拦截。
自定义平台服务的目录约定
在源码里新增一个平台级服务(service),通常铺在三处目录:
src/vs/platform/<服务名>/ # 服务定义、接口、类型(common 子目录放跨进程共享类型)
src/vs/workbench/services/<服务名>/ # workbench 层的服务实现
src/vs/workbench/contrib/<服务名>/ # 贡献点(UI、命令、视图等)
引用与注册位置:
src/vs/workbench/workbench.desktop.main.ts # desktop 入口汇总
src/vs/workbench/workbench.common.main.ts # 通用入口汇总
src/vs/code/electron-main/app.ts # 主进程服务注册
原文链接:https://www.ssssmy.com/notes/vscode-yuan-ma-er-ci-kai-fa-chang-yong-ji-zhi-su-ji