专注于前端开发, 追求更好的用户体验, 更好的开发体验 [长沙前端QQ群:234746733]

VS Code Extension - VSCode 插件开发

Developing

# 生成脚手架
npx --package yo --package generator-code -- yo code
# - What type of extension do you want to create? # 插件类型选择
# > New Extension (TypeScript) # 基于TS的插件
#   New Extension (JavaScript) # 基于JS的插件
#   New Color Theme # 颜色主题
#   New Language Support # 语言支持(语法高亮/代码片段/代码检查)
#   New Code Snippets # 代码片段(插入常用代码或模板)
#   New Keymap # 快捷键(定义快捷键)
#   New Extension Pack # 插件包(包含多个插件, 可一次性安装这个包来获得一组功能)
#   New Language Pack (Localization) # 语言包(本地化支持)
#   New Web Extension (TypeScript) # Web 插件(在 VSCode 的 web 版本中运行)
#   New Notebook Renderer (TypeScript) # Notebook渲染器(在 VSCode 的笔记本视图中渲染, 包含可执行代码块和展示结果区域, 场景: 可视化/教程/演示文档, 展示图表/表格等)
# - What's the name of your extension? # 如 Xxx for VS Code
# - What's the identifier of your extension?  # 插件标识符ID (aaa-bbb)
# - What's the description of your extension? # 插件描述
# - Initialize a git repository? # 是否初始化git仓库 (默认yes)
# - Bundle the source code with webpack? # 是否使用webpack打包源码 (默认no, 用tsc编译)
# - Which package manager to use? # 使用哪个包管理器 (pnpm/yarn)
# - Do you want to open the new folder with Visual Studio Code? # 选择code打开插件目录, 提示安装`Extension Test Runner`(安装)

插件入口为 src/extension.ts(建议src改成client). 按F5或 VSCode 左下角的Run Extension, 启动调试窗口, 在这个实例中可以调试插件.
调试窗口, 按 Ctrl+Shift+P 打开命令面板, 输入 "Hello World" 并运行, 右下角会弹出提示.
调试窗口, 按 Ctrl+Shift+I 打开开发者工具;
修改源码后, 在源码窗口按Ctrl+Shift+F, 或 在调试窗口 按 Ctrl+R 重载插件(Reload Window), 使修改生效.
调试窗口, 按Ctrl+Shift+PWebview可选Webview开发者工具/刷新Webview.

# 后续 调整
# pnpm add -D prettier prettier-eslint # eslint
# pnpm add -D esbuild # 改成 esbuild 打包
  # "vscode:prepublish": "pnpm run compile", 改成
  # "vscode:prepublish": "npm run esbuild-base -- --minify",
  # "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
  # "_vscode:prepublish": "pnpm run compile",
# echo -e 'pnpm-lock.yaml\nyarn.lock' >> .vscodeignore # 打包忽略
# mv .eslintrc.json .eslintrc.js # eslint 改成js

Structure

|-- .eslintrc.json # eslint配置
|-- .npmrc # npm配置
|-- .vscode # VSCode配置
|   |-- extensions.json # 插件依赖
|   |-- launch.json # debug调试配置
|   |-- settings.json # 设置
|   `-- tasks.json # 任务配置
|-- .vscodeignore # 插件打包忽略文件
|-- CHANGELOG.md # 更新日志
|-- README.md # 说明文档
|-- assets # 静态资源 resources
|   |-- logo.png # 插件市场图标(png/jpeg) [email protected], 128x128 or 256x256
|   `-- logo.svg #
|-- package.json # 插件清单
|-- src # 源码
|   |-- extension.ts # 插件入口
|   `-- test # 测试
|       `-- extension.test.ts
|-- tsconfig.json # ts配置
`-- vsc-extension-quickstart.md # 快速开始文档
# |-- languages
# |-- snippets
# |-- syntaxes
# |-- themes

Publish

npm install -g @vscode/vsce # 本地打包 依赖
# vsce ls --no-dependencies # 列出所有将打包到.vsix的文件
# 打包成 .vsix
vsce package # npm
vsce package --no-dependencies # pnpm [Support pnpm](https://github.com/microsoft/vscode-vsce/issues/421#issuecomment-1038911725)
vsce package --yarn # yarn
# vsce login <publisher id>
vsce publish # 推送到 VS Code Marketplace (同样依赖 --no-dependencies / --yarn 参数)
# vsce publish minor # major, minor, patch # 大/小/补丁 版本
# 更新 插件版本 package.json 中的 version
pnpm version patch # major, minor, patch [语义化版本](https://semver.org/lang/zh-CN/)

Docs

[Readme gif动画]

win - ScreenToGift, Mac: LICEcap / GIPHY Capture

VSCode Portable 版本

Windows x64 zip: https://update.code.visualstudio.com/{version}/win32-x64-archive/stable
解压后创建个data文件夹, 里面放extensions文件夹, 里面放插件文件夹, 重启VSCode即可加载插件.
1.92.2

Extension API - 插件官方文档

breakpoints # 断点, 定义插件支持在哪些语言设置断点
colors # 颜色
commands # 命令
configuration # 配置
configurationDefaults # 配置默认值
customEditors # 自定义编辑器
debuggers # 调试器, 通过VS Code DAP协议与IDE通信
grammars # 语法高亮规则
icons # 图标
iconThemes # 图标主题
jsonValidation # JSON验证
keybindings # 快捷键
languages # 语言
menus # 菜单
problemMatchers # 错误匹配(lint)
problemPatterns # 定义匹配模式
productIconThemes # 产品图标主题
resourceLabelFormatters # 资源标签格式化
semanticTokenModifiers # 语义标记修饰符
semanticTokenScopes # 语义标记范围
semanticTokenTypes # 语义标记类型
snippets # 代码片段
submenus # 子菜单
taskDefinitions # 任务定义
terminal # 终端
themes # 主题
typescriptServerPlugins # TypeScript服务插件
views # 视图
viewsContainers # 视图容器
viewsWelcome # 欢迎视图
walkthroughs # 演练
# package.json, activationEvents
onLanguage:${language} # 打开某种语言的文件时, e.g. "onLanguage:json", "onLanguage:typescript"
onCommand:${command} # 调用命令时, e.g. "hello-world.helloWorld" (VSCode>=1.74.0, 插件内置命令无需写到这里, 即可激活)
onDebug # 调试模式时
onDebugInitialConfigurations
onDebugResolve
workspaceContains:${filename} #打开特定文件时, e.g. "workspaceContains:**/.editorconfig"
onFileSystem
onView:${viewId} # 打开侧栏指定 id 的视图时, e.g. "onView:nodeDependencies"
onUri
onWebviewPanel
onCustomEditor
onAuthenticationRequest
onStartupFinished # 类似*, 但不会减慢 VSCode 的启动速度, 在用 * 标记的插件激活后触发
* # VSCode启动时, 官方不建议使用
// package.json
// 激活事件
  "activationEvents": [
    "onStartupFinished"
  ],
// 插件入口
  "main": "./out/extension.js",
// 贡献点关联
   "contributes": {
    // 插件配置
    "configuration": {
      "title": "Demo Extension",
      "properties": {
        "YourExtName.sampleSetting": {
          "description": "Sample setting",
          "type": "boolean"
        }
      }
    },
    // 注册命令
    "commands": [
      {
        "command": "hello-world.helloWorld",
        "title": "Hello World"
      }
    ],
    // viewContainer: 视图容器 (view视图的key要与viewContainer的id一致)
    "viewsContainers": {
      // 注册 活动栏(Activity Bar)
      "activitybar": [
        {
          "id": "hello-world",
          "title": "视图容器名",
          "icon": "assets/icon.svg"
        }
      ],
      // 注册 底部面板(Panel Area)
      "panel": [
        {
          "id": "hello-panel",
          "title": "容器 - Panel",
          "icon": "assets/logo.svg"
        }
      ]
    },
    // 注册视图
    // vscode.window.registerWebviewViewProvider(viewType, sidebarViewProvider)
    // window.createTreeView("demo.tree", { treeDataProvider: demoDataProvider });
    "views": {
      "hello-world": [
        {
          "id": "demo.sidebar",
           "type": "webview", // webview类型
          "name": "视图1"
        },
        {
          "id": "demo.tree",
          "name": "视图2"
        }
      ],
      "hello-panel": [
        {
          "id": "demo.panel",
          "type": "webview",
          "name": "视图 - Panel"
        }
      ]
    },
    // 注册菜单 - view/title: 视图标题栏; view/item/context: 视图右键菜单; editor/context: 编辑器右键菜单
    "menus": {
      "view/title": [
        {
          "command": "hello-world.helloWorld",
          "when": "view == demo.sidebar",
          "group": "navigation"
        },
        {
          "command": "hello-world.helloWorld",
          "when": "view == demo.tree"
        }
      ]
    },
    // 注册 视图的 欢迎页 (tree类型 视图为空时显示)
    "viewsWelcome": [
      {
        "view": "demo.tree",
        "contents": "No data found [learn more](https://www.npmjs.com/), [refreshData](command:YourExtName.refreshData), \n[helloWorld](command:YourExtName.helloWorld)"
      }
    ],
    // 注册欢迎页, 安装扩展程序时会自动打开, 开发环境`Ctrl+Shift+P`选择`Open Walkthrough`打开
    "walkthroughs": [
      {
        "id": "hello-world.walkthroughs",
        "title": "Demo Walkthroughs",
        "description": "A sample walkthrough",
        "steps": [
          {
            "title": "步骤1",
            "id": "runcommand",
            "description": "描述1\n[Run Command](command:YourExtName.addEntry)",
            "media": {
              "image": "assets/logo.png",
              "altText": "Empty image"
            },
            // "completionEvents": [
            //   "onCommand:YourExtName.addEntry"
            // ]
          },
          {
            "title": "步骤2",
            "id": "changesetting",
            "description": "描述2\n[Change Setting](command:YourExtName.changeSetting)",
            "media": {
              "markdown": "README.md"
            },
            // "completionEvents": [
            //   "onSettingChanged:YourExtName.sampleSetting"
            // ]
          }
        ]
      }
    ],

    // 插件支持的语言定义, id: 语言ID, aliases: 别名, extensions: 文件扩展名, filenames: 文件名(可选, 优先`extensions`), configuration: 语言配置(可选, 定义注释语法/括号配对等)
    "languages": [
      {
        "id": "json",
        "aliases": ["JSON", "json"],
        "extensions": [".json"],
        "filenames": ["composer.json", "package.json"],
        "configuration": "./language-configuration.json"
      }
    ],
  },

vscode 官方插件例子

npx degit microsoft/vscode-extension-samples/getting-started-sample getting-started-sample # 入门示例
npx degit microsoft/vscode-extension-samples/chat-sample chat-sample # 集成到 Copilot Chat 示例

Examples

资源路径

// client/extension.ts
vscode.Uri.joinPath(context.extensionUri, "assets", "cat.png"); // 插件资源路径

信息提示框

// client/extension.ts
// activate 插件被激活时的回调函数
export function activate(context: vscode.ExtensionContext) {
  console.log('"hello-world" is now active!'); // 打印日志(在源码窗口的`DEBUG CONSOLE`中查看)

  // registerCommand 注册命令, 参数: 命令ID, 回调函数
  let disposable = vscode.commands.registerCommand("hello-world.helloWorld", () => {
    // showInformationMessage 显示信息提示框(右下角)
    vscode.window.showInformationMessage("HelloWorld!");
  });
  // context.subscriptions.push 将命令注册到插件上下文中
  context.subscriptions.push(disposable);
}

// client/extension.ts // vscode.window.showInformationMessage 显示信息提示框
// import * as os from "os";
// import { exec } from "child_process";

const header = "Message Header";
const options: vscode.MessageOptions = {
  detail: "<b>Message</b> Description",
  modal: true, // 是否模态 (中间true, 右下角false 默认)
};
vscode.window.showInformationMessage(header, options, ...["Ok"]).then((result) => {
  console.log(result);
  if (result === "Ok") {
    if (os.platform() === "win32") {
      exec(`start cmd`);
    } else if (os.platform() === "darwin") {
      exec(`open -a Terminal`);
    } else if (os.platform() === "linux") {
      exec(`gnome-terminal`);
    }
  }
});

输入框

// client/extension.ts // vscode.window.showInputBox 显示输入框 (输入并更新配置)
const configuration = vscode.workspace.getConfiguration("hello-world");
vscode.window
  .showInputBox({
    prompt: "Enter the URL of your API Server",
    value: configuration.get("serverUrl", ""),
    placeHolder: "URL",
  })
  .then((url) => {
    if (url) {
      console.debug("Set Server URL: ", url);
      configuration.update("serverUrl", url, vscode.ConfigurationTarget.Global, false);
    }
  });

Webview

const panel = vscode.window.createWebviewPanel(
  "helloWorld", // viewType唯一标识, 如 helloWorld
  "Hello World", // 面板标题
  vscode.ViewColumn.One, // 显示位置(vscode可分多列显示), Active(当前活动的列) / Beside(当前活动列的旁边) / One(第一列) / Two(第二列) / Three(第三列)
  {
    enableScripts: true, // 是否允许脚本
  }
);
// 设置HTML内容
panel.webview.html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="root">HelloWorld</div></body></html>`;
// 面板关闭时
panel.onDidDispose(
  () => {
    console.log("dispose");
  },
  null,
  context.subscriptions
);

Refs

FAQs

vscode 的主要区域

活动栏(Activity Bar) - 最左侧图标列; 侧边栏(Sidebar) - 活动栏右侧; 编辑器(Editor Area) - 编辑文件; 面板(Panel Area) - 底部; 状态栏(Status Bar) - 最底部;

已安装插件目录

~/.vscode/extensions # Git Bash/Linux/MacOs
$env:UserProfile\.vscode\extensions # windows PowerShell
%USERPROFILE%\.vscode\extensions # windows cmd

web方式 debug

pnpm add -D @vscode/test-web
# package.json
# "main": "./out/extension.js", // 下面增加
# "browser": "./out/extension.js",
# "scripts": {
#   "web": "vscode-test-web --port=3456 --browser=none --extensionDevelopmentPath=.",
# }
#  --browser=chromium 可打开独立的浏览器窗口
# 依赖 playwright, 如报错可手动安装: npm i -g playwright && npx playwright install chromium

hot reload 暂不支持

Hot reload in extension
在调试窗口 按 Ctrl+R 重载插件(Reload Window), Ctrl+Shift+P, 搜Reload Webviews刷新Webview.
Hot Module Replacement

// 自定义实现: 再 panel 显示时, 监听文件变化, 重载Webview
const filePath = path.join(__dirname, "..", "webview-ui", "dist", "index.html");
this._watcher = fs.watch(filePath, { recursive: false }, (eventType, filename) => {
  // console.log(eventType, filename);
  if (eventType === "change") {
    // window.showInformationMessage("reload");
    // commands.executeCommand("workbench.action.reloadWindow"); // 重载窗口
    commands.executeCommand("workbench.action.webview.reloadWebviewAction"); // 刷新Webview
    // const scriptUri = this._panel.webview.asWebviewUri(Uri.file(filePath));
    // this._panel.webview.html = this._getWebviewContent(this._panel.webview, scriptUri);
  }
});
// onDidDispose(() => {
//   this._watcher.close();
// });

测试 light/dark 主题

Ctrl+Shift+P, 搜toggle between light/dark

Webview 的 window 对象

使用 acquireVsCodeApi() 获取vscode WebviewApi. 仅提供了有限的API: postMessage, getState, setState, 用于消息传递和状态管理.

i18n 多语言

官方有提供 vscode-nls 包, 用于多语言支持(不支持webview).
@vscode/l10n
[l10n参考](https://www.eliostruyf.com/localization-webviews-visual-studio-code/

pnpm add @vscode/l10n # 安装
# bundle.l10n.zh-cn.json # vscode 切换语言时, 会自动加载对应的语言包.
# 测试 不默认加载 bundle.l10n.json/bundle.l10n.en.json [暂不支持 设定默认的语言包](https://github.com/microsoft/vscode/issues/168127)
# import * as l10n from "@vscode/l10n";
# l10n.config({ uri: xxx }); // 配置语言包路径

侧边栏 icon

除了内置的图标, 还可以通过 contributes.icons 自定义图标(需woff图标).

// "contributes"
"icons": {
  "test-icon": {
    "description": "chat-history",
    "default": {
      "fontPath": "./assets/remixicon.woff",
      "fontCharacter": "\\EB60"
    }
  }
},
"commands": [
  {
    "command": "hello-world.helloWorld",
    "title": "test",
    "icon": "$(test-icon)"
  },
]

Markdown 预览

# markdown-it/remarkjs 方便扩展
# https://npmtrends.com/markdown-it-vs-marked-vs-remark
# [踩了marked.js的坑,选择了markdown-it](https://www.91temaichang.com/2023/03/18/the-marked-and-markdownit/)
pnpm add -D markdown-it @types/markdown-it
pnpm add -D highlight.js # clipboard

# 扩展vscode的markdown预览
"markdown.markdownItPlugins": true,
"markdown.previewScripts": [
"client/assets/mermaid.min.js", # 要 https://cdn.jsdelivr.net/npm/mermaid@8/dist/mermaid.min.js @9/@10 类图可能报错
"client/assets/markdown-it-preview.js"
],
# const observer = new MutationObserver(applyCurrentTheme);
#   observer.observe(document.body, { attributes: true, attributeFilter: ["class"] }); // 监听主题变化
# window.addEventListener("vscode.markdown.updateContent", () => {}) // md更新内容事件

流式问答 stream 测试

const http = require("http");
const server = http.createServer((req, res) => {
  // 流header
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
  res.setHeader("Transfer-Encoding", "chunked");
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Headers", "*");
  // 发送消息
  const timer = setInterval(() => {
    res.write(JSON.stringify({ msg_id: 666, content: Math.random() + ", " }) + "\n");
  }, 500);
  setTimeout(() => {
    clearInterval(timer);
    res.end(JSON.stringify({ content: "end" }));
  }, 5000);
});
server.listen(3012, () => console.log("服务器正在监听 3012 端口"));

获取文件的函数列表

需要安装Language Server, 比如
Java: Extension Pack for Java, JDK

// vsccode自带展示变量/函数列表: vscode.commands.executeCommand('workbench.action.gotoSymbol'); // ctrl+shift+o // ctrl+shift+p - focus on outline view
vscode.commands.executeCommand("vscode.executeDocumentSymbolProvider", doc.uri).then((symbols: any = []) => {
  const functions = symbols.filter((symbol: any) =>
    [vscode.SymbolKind.Function, vscode.SymbolKind.Method].includes(symbol.kind)
  );
  console.log("DocumentSymbol", symbols, functions);
});

右键 子菜单

"contributes": {
  "submenus": [
    {
      "id": "test.submenu",
      "label": "test submenu"
    }
  ],
  "menus": [
    "editor/context": [
      {
        "when": "editorHasSelection", // 选中文字时
        "submenu": "test.submenu",
        "group": "1_modification"
      }
    ],
    "test.submenu": [
      {
        "command": "workbench.action.gotoSymbol"
      }
    ]
  ]
}

/ 分类: 开发 / TrackBackhttps://xhl.me/archives/vscode-extensions/trackback标签: 插件, vscode

添加新评论 »