VSCode 有非常强大的功能,原因在于它的很多功能都是基于插件来实现的,可以使用插件来丰富和扩展它的功能。
但是我们的需求总是复杂多变的,总有一些场景是现有的插件无法满足,这时候就需要我们借助 VSCode 的开放接口,手动实现我们需要的功能。
这篇文章主要带大家从一个简单插件开发入手来介绍插件开发的一些具体事项,最后根据实际需要实现一个插件。
VSCode 插件能做什么
- 自定义命令、快捷键
- 自定义上下文菜单操作,如:平时我们右键的菜单栏
- 自定义跳转、自动补全、悬浮提示
- 自定义主题
- 自定义代码片段
- 新增语言支持
- 语法检查
- 语法高亮
- 代码格式化
- ……
新手教程——如何编写一个插进
VSCode 官方提供了脚手架 yomen,用它来生成项目:
npm install -g yo generator-code
初始化一个插件项目,这个脚手架会生成一个可以立马开发的项目:
yo code
选择生成插件(JS 或 TS),初始化过程中需要我们做一些偏好设置,按照需求选择:
打开生成的项目,其中最重要的两个文件是插件的入口 extension.js
和配置文件 package.json
:
注册命令
extension.js
文件主要会导出两个方法:activate
和 deactivate
:
- activate,插件被激活时执行的方法;
- deactivate,插件被销毁时调用的方法;
在插件被激活时(activate方法内),使用vscode.commands.registerCommand
来注册命令,第一个参数是命令ID,第二个参数是执行命令后的回调函数。
例如,下面这段代码:
// 入口文件 extension.js
const vscode = require('vscode');
function activate(context) {
let disposable = vscode.commands.registerCommand('helloWorld', function () {
vscode.window.showInformationMessage('Hello World from !');
});
context.subscriptions.push(disposable);
}
function deactivate() { }
module.exports = {
activate,
deactivate
}
注册了一个命令 helloWorld
。当用户执行 helloWorld
命令,就会调用对应的处理函数。你也可以通过 vscode.commands.executeCommandAPI
去调用它。
创建面向用户的命令
vscode.commands.registerCommand
仅仅是将命令id绑定到了处理函数上,如果想让用户从命令面板中搜索到这个命令,还需要在 package.json
中配置对应的命令配置项:
{
"contributes": {
"commands": [
{
"command": "helloWorld",
"title": "Hello World"
}
]
}
}
上面的配置,给命令 helloWorld
设置了一个 title Hello World
,通过设置title控制命令在UI中的显示。在搜索命令时,显示的是标题 Hello World
。
调试
在开发插件的时候,按下F5就会弹出一个新的 VSCode 窗口。
新窗口标题会注明扩展开发宿主,表示是插件调试,新窗口已经成功加载了我们的插件:
按下Ctrl+Shift+P
搜索命令,输入刚刚注册的命令Hello World
,选择执行命令,就会触发命令的回调函数(右下角弹出Hello World from !
提示)
最简单的插件就实现了!
接下来,了解一下插件的一些配置字段。
插件激活
插件并不是安装启用之后就会激活。像这样,这个插件就处于安装启用但是尚未激活状态:
插件具体什么时候激活是在
package.json
中的 activationEvents
声明。所有插件都是按需加载的,每个插件都应该声明具体的加载时机。以下是几种常用的声明方式:
-
*
,VSCode 启动时,插件开始触发:
{
"activationEvents": ['*']
}
-
onStartupFinished
,VSCode 启动一段时间后才会激活插件。类似于*
类激活事件,但它不会减慢 VSCode 启动,该事件在所有*
类插件激活完成后触发:
{
"activationEvents": [
"onStartupFinished"
]
}
-
onCommand:Hello World
, 当执行命令 Hello World 时激活插件:
"activationEvents": [
"onCommand:Hello World"
]
-
onLanguage:languageId
, 编辑区包含特定语言文件时激活插件。
只要编辑区(上图红色区域)打开 json、markdown 或者 ts文件时,激活该插件:
"activationEvents": [
"onLanguage:json",
"onLanguage:markdown",
"onLanguage:typescript"
]
这几个激活事件可以覆盖大部分情况,还有其他一些激活事件,没有一一列举,感兴趣的可以阅读一下官方文档。
内置命令
在开发插件的时候,经常需要通过 VSCode 提供的 API 来进行一些UI操作,例如打开文档、修改并保存文档内容、关闭文档、刷新文件夹等。
使用 vscode.commands.executeCommandAPI
可以调用一个命令,你可以通过它将 VSCode 的内置函数构建在你的插件中。
例如,下面的代码用来关闭 VSCode 当前打开的文档:
vscode.commands.executeCommand('workbench.action.closeActiveEditor');
关闭所有打开的文档:
vscode.commands.executeCommand('workbench.action.closeAllEditors');
效果就和这个一样
刷新当前工作区文件夹:
vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');
更多命令详见:
有些功能可以通过查看 VSCode 的官方文档找到对应的 API 接口和相应的对象,但很多功能是没有写到文档上的。
最简单的查找命令的方式,直接看 VSCode 的键盘快捷方式
contributes
contributes
是 VSCode 插件开发中的一个重要配置属性,用来定义插件的命令、快捷键、语言支持、主题、配置等。
下面列举一些常用的属性及其作用:
命令配置
contributes.commands,定义插件提供的命令。
包括命令的标识符(command)、命令的标题(title)。
一个命令注册完后,需要在 commands中配置,用户才能搜索到该命令。
{
"contributes": {
"commands": [
{
"command": "helloWorld",
"title": "Hello World"
}
]
}
}
配置右键菜单
右键菜单有很多配置,这里只列举比较常见的。
编辑器面板菜单
editor/context
配置命令什么时候出现在编辑文件右键菜单中:
{
"contributes": {
"menus": {
"editor/context": [
{
"command": "helloWorld",
"group": "navigation",
"when": "true"
}
]
}
}
}
-
command
定义菜单被点击后要执行什么操作; -
group
定义菜单分组; -
when
控制菜单合适出现;
when语句语法有很多,这里列举几个常用的:
- resourceLangId == javascript:当编辑的文件是js文件时;
- resourceFilename == test.js:当当前打开文件名是test.js时;
- isLinux、isMac、isWindows:判断当前操作系统;
- editorFocus:编辑器具有焦点时;
- editorHasSelection:编辑器中有文本被选中时;
- view == someViewId:当当前视图ID等于someViewId时;
- ……
多个条件可以通过与或非进行组合,例如:editorFocus && isWindows && resourceLangId == javascript。
资源管理器面板菜单
explorer/context
配置命令什么时候出现在资源管理区右键菜单中:
{
"contributes": {
"menus": {
"explorer/context": [
{
"command": "helloWorld",
"group": "navigation",
"when": "true"
}
]
}
}
}
右键打开资源管理区菜单,多了一个 Hello World
命令,如下:
控制命令何时可见
有些命令是场景相关的,比如在特定的语言的编辑器中,或者只有用户设置了某些选项时才展示。
menus.commandPalette
限制命令出现在命令面板的时机,你需要配置命令ID和一条when语句:
{
"contributes": {
"menus": {
"commandPalette": [
{
"command": "helloWorld",
"when": "editorLangId == markdown"
}
]
}
}
}
现在只有在编辑器打开 Markdown 文件,才能搜索到命令 helloWorld
。
快捷键
快捷键设置的写法比较简单,contributes.keybindings
:
"contributes": {
"keybindings": [
{
// 指定快捷键执行的操作
"command": "helloWorld",
// windows下快捷键
"key": "ctrl+shift+l",
// mac下快捷键
"mac": "cmd+shift+l",
}
]
}
这个快捷键最终会在命令后面提示快捷键
代码片段
snippets
,输入一个前缀,有许多提示,选择回车后生成代码。
提供了一些快捷键来帮助你更快速地使用代码片段。
// 配置
"contributes": {
"snippets": [
{
"language": "typescript",
"path": "./snippets/index.json"
}
]
}
这里 language
设置了 snippets
作用于哪种语言(打开文件时判断),path
设置了代码片段地址:
// snippets/index.json:
{
"Snippets测试": {
"prefix": "hello",
"body": [
"console.log($1)"
],
"description": "描述"
}
}
"Snippets测试"
,自定义snippet的名称;
"prefix"
, 输入什么可以出现snippets的提示;
"body"
, 按回车后出现的一大段代码,是一个数组,数组里面是字符串,每个字符串代表一行代码,{2}表示第二个光标的位置;
"description"
, 对于这个snippet的描述,当我们选中这个snipets提示时,描述会出现在后面。
配置
configuration
,通过这个配置项我们可以设置属性,这个属性可以在 vscode 的 settings.json
中设置,然后在插件工程中可以读取用户设置的这个值,进行相应的逻辑。
"contributes": {
"configuration": {
// 显示在配置页左侧
"title": "测试configuration",
"properties": {
// 全局唯一的配置ID
"test_boolean": {
"type": "boolean",
"default": false,
"description": "测试布尔"
},
// 全局唯一的配置ID
"test_number": {
"type": "number",
"default": 14,
"description": "测试number"
},
// 全局唯一的配置ID
"test_string": {
"type": "string",
"default": "1",
"description": "测试字符串"
}
}
}
}
打开设置页,搜索配置如下:
读取用户配置的值:
function activate(context) {
let disposable = vscode.commands.registerCommand('helloWorld', function () {
// 获取用户设置
const configurate = vscode.workspace.getConfiguration().get('test_string')
vscode.window.showInformationMessage(configurate);
});
}
执行命令 helloWorld
:
其他
- languages:定义插件对特定语言的支持,可以为特定的文件类型提供语法高亮、代码片段、自动完成等功能。
- grammars: 语法
- debuggers:调试
- breakpoints:断点
- themes:主题
- jsonValidation:自定义JSON校验
- views:左侧侧边栏视图
- viewsContainers:自定义activitybar
- ……
实践——自定义插件
针对业务,可以开发这样一个插件:
- 右键点击文件夹,在菜单中增加两个选项:
- "生成ReactLynx页面"
- "生成ReactLynx组件"
具体功能:
- 根据"文件夹名称",写入预先定义好的代码,类名首字母大写
- 把代码写入 "文件夹/index.tsx" 文件中
- 刷新文件目录,打开文件
新建项目
右键生成组件/页面
注册命令
在插件里面注册两个命令 reactLynxPage 和 reactLynxComponent, 根据命令执行 generateCode 函数:
export function activate(context: vscode.ExtensionContext) {
let pageDisposable = vscode.commands.registerCommand(pageCommand, (url: vscode.Uri) => generateCode(url, pageCommand));
let componentDisposable = vscode.commands.registerCommand(componentCommand, (url: vscode.Uri) => generateCode(url, componentCommand));
context.subscriptions.push(pageDisposable);
context.subscriptions.push(componentDisposable);
}
在 generateCode 函数中,包含两个参数 url 和 commandId:
- 如果是通过右键点击文件执行命令,参数 url 才有文件夹地址,如果不存在 url?.fsPath 则为不规范的命令执行;
- 通过 commondId 判断是生成组件还是生成页面;
function generateCode(url: vscode.Uri, commandId: string) {
// 获取右键点击的文件夹路径, 绝对路径
const folderPath = url?.fsPath;
if (!folderPath) {
return;
}
// …… 省略一些判断逻辑
// 获取文件夹名称, 首字母大写, 改为驼峰式命名
// 生成代码片段内容
const codeSnippet = commandId === pageCommand ? generatePageSnippet(name) : generateComponentSnippet(name);
// ......
}
folderPath 是文件夹的绝对路径,那么该文件夹下组件或页面的地址就是 ${folderPath}/index.tsx
,样式文件地址 ${folderPath}/index.less
。
通过 fs.existsSync
判断文件是否存在,文件已存在则不生成(防止误操作),如果不存在则生成文件写入代码。
// 待生成文件地址
const tsxFileName = `${folderPath}/index.tsx`;
const styleFileName = `${folderPath}/index.less`;
// 将代码片段写入文件
if (fs.existsSync(tsxFileName)) {
vscode.window.showInformationMessage('该目录下已经存在index.tsx');
}
else {
fs.writeFileSync(tsxFileName, codeSnippet);
}
if (fs.existsSync(styleFileName)) {
vscode.window.showInformationMessage('该目录下已经存在index.less');
}
else {fs.writeFileSync(styleFileName, `.container {
}`);}
}
通过 vscode 提供的内置命令刷新文件目录:
vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');
最后,在编辑区自动打开生成的 index.tsx :
vscode.workspace.openTextDocument(tsxFileName).then(document => {
vscode.window.showTextDocument(document, {
selection: new vscode.Range(0, 0, 0, 0)
});
}, () => {
vscode.window.showErrorMessage(`打开${tsxFileName}失败!`);
});
配置命令
注册完命令之后,在 package.json
配置 contributes.commands
,这样就能使用 reactLynxPage 和 reactLynxComponent 这两个命令了。
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "reactLynxPage",
"title": "生成ReactLynx页面"
},
{
"command": "reactLynxComponent",
"title": "生成ReactLynx组件"
}
]
}
继续配置 contributes.menus
。当右键点击文件夹("when"等于"explorerResourceIsFolder"),在弹出的菜单中增加 "生成ReactLynx页面" 和 "生成ReactLynx组件" 两个选项。
"contributes": {
// .......
"menus": {
"explorer/context": [
{
"command": "reactLynxPage",
"group": "1_modification",
"when": "explorerResourceIsFolder"
},
{
"command": "reactLynxComponent",
"group": "1_modification",
"when": "explorerResourceIsFolder"
}
]
}
},
group
是分组,下图是 vscode 官方菜单分组:
配置
commandPalette
隐藏命令,无法在命令面板中搜索到 ,但是右键菜单的命令能照常执行。
"contributes": {
// .......
"menus": {
"explorer/context": [
// .......
],
"commandPalette": [
{
"command": "reactLynxPage",
"when": "false"
},
{
"command": "reactLynxComponent",
"when": "false"
}
],
}
},
打包
安装对应的模块 vsce
npm i vsce -g
利用 vsce 打包,生成对应的 vsix 插件文件
vsce package
如果不想发到插件市场,直接右键安装到插:
发布
注册开发者账号,发布到官网应用市场……
总结
以上就是一个简单的入门级实战教程,带大家了解开发一个 VSCode 插件的基本思路。插件开发,说简单也简单,说复杂也不简单,后续大家如果遇到更复杂更定制化的需求,基本上需要查阅 官方文档 深入学习。