s-blog

VSCode 源码二次开发:常用机制速记

ssssmy · 2026-06-05 · 5 min · VsCode插件开发

VSCode 源码二次开发:常用机制速记

做 fork 版 VSCode 源码二次开发时攒下的一些零碎机制笔记。

插件的加载与激活流程

VSCode 插件从市场到运行大致四个阶段:

  1. 列表生成:向扩展市场服务端 POST 查询(target = Microsoft.VisualStudio.Code),拿到插件列表渲染 UI。
  2. 下载安装:从 CDN 下载 .vsix(本质是 zip),解压到用户目录 .vscode/extensions,读出 package.json
  3. 激活:按 package.jsonmain 字段定位入口,用 Node require 加载,执行导出的 activate()。激活时机由 activationEvents(如 onLanguage:pythononCommand:xxx)控制,实现懒加载。
  4. 通信:插件跑在独立扩展宿主进程,通过 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.tssrc/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