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+P
搜Webview
可选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) icon@package.json, 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 - 插件官方文档
- Your First Extension - 第一个插件
- Common Capabilities - 常用功能
- Webview - 渲染html
- Chat extensions 集成到 Copilot Chat
- Webview 不支持 alert、confirm 和 prompt, 资源加载需要通过
asWebviewUri
方法 - 加载外部资源需要配置
contentSecurityPolicy
选项 `<meta http-equiv="Content-Security-Policy" content="..." /> - UX Guidelines - UX 指南
- Publishing Extensions - 发布插件
- Bundling Extensions - 打包插件
- VS Code API - API 文档
- Contribution Points - 贡献点
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"
}
],
},
- Built-in Commands - 内置命令
- When clause contexts - 条件上下文
- Theme Color - 主题颜色
- Product Icon Reference - 产品图标
Codicons Icons - Document Selector - 文档选择器
vscode 官方插件例子
- vscode-webview-ui-toolkit-samples
- vscode-chatgpt
- vscode-chatgpt-reborn
- COBOT-SCA 软件成分分析与同源漏洞检测
- GitLens
- IBM Blockchain Platform
- RemixIcon Apache许可证(可商用, 禁止 商标使用/任何形式转售)
- CursorCode Cursor GPT vscode扩展插件
- twinny 代码补全/chat/用例生成
- vscode-messenger RPC消息传递
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
- VS Code 插件开发中文文档
- VS Code插件开发文档
- Notebook 介绍
- Using Vite for bundling VS Code extensions
- Vscode 的 extension webview 开发示例
- Webview的使用
- 一个VSCode插件
- VSCode Webview 完美集成 Webpack 热更新 vite应该可以类似处理(开发环境使用http的bundle, 生产环境使用file的bundle)
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 预览
- remarkjs react-markdown
- markdown-it vscode预览使用的是markdown-it
markdown-it插件开发指南 mermaid 展示流程图等
- markdown-it-mermaid-less 不能扩展extendMarkdownIt
- @markslides/markdown-it-mermaid 不能扩展extendMarkdownIt
- markdown-it-mermaid 不能扩展extendMarkdownIt
- marked.js vscode-chatgpt 使用
# 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"
}
]
]
}