Appearance
vsCode 插件开发
引言:
vsCode作为宇宙最强编辑器
,除了可以摸鱼炒股外,它甚至能开发插件来提升生产效率;我作为前端切图仔中的一员,日常除了上班摸鱼逛论坛,还在思考怎么样才能提升我们的编码能力,使得快速、有效地交付产品需求,避免重复性的操作,让自己从繁琐的劳动的解脱出来;
vsCode是我重度使用的编码工具,从前端=》后端=》云开发,它都能胜任(“宇宙最强”当之无愧),作为一款优秀的编辑器软件,它自身的能力的优秀和灵活的插件拓展机制有关,正如我前面说到的减少重复性的操作,在vsCode强大的插件机制下,我们可以实现很多我们能想到甚至想不到的功能,一款优秀的插件甚至能解放你的大部分劳动;
前期准备
模板搭建
根据官方文档的入门demo搭建方式来初始化模板;
# 安装脚手架,yo是著名的脚手架模板工具,generator-code是搭配yo的vsCode脚手架工具插件
npm install -g yo generator-code
# 脚手架搭建ts模板
yo code
# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? () HelloWorld
# ? What's the identifier of your extension? (helloworld)
# 注意:这里的插件id是指VsCode插件市场的唯一标识(账户名.插件id的格式组成唯一标识)
# ? What's the description of your extension? ()
# ? Initialize a git repository? (Y/n) Y
# ? Bundle the source code with webpack? (y/N) y
# ? Which package manager to use? yarn
至此,TypeScript
版的VsCode插件模板就准备好了,在vsCode中按下F5
即可进行调试,初始化后的项目目录为:
helloworld
├─ .eslintrc.json
├─ .gitignore
├─ .vscode
├─ .vscodeignore
├─ .yarnrc
├─ CHANGELOG.md
├─ package.json // 插件manifest配置
├─ README.md
├─ src
│ ├─ extension.ts // 插件入口文件
│ └─ test
├─ tsconfig.json
├─ vsc-extension-quickstart.md
├─ webpack.config.js
└─ yarn.lock
其中最重要的是package.json
,它相当于整个插件项目的manifest,在package.json
中默认使用extension.js
为插件入口文件,并且通过activationEvents
配置项指定插件激活时机,可根据实际的开发需求指定特定语言触发、特定指令触发、特定窗口触发等等,该触发动作用于激活入口文件extension.js
中默认导出的activate
函数来注册插件指令(未配置插件激活时机或插件未处于激活时机则无法使用插件相关的功能);
默认模板自带hello world
指令示例,具体触发流程如下:
指令
通过上一章节练习,一个简单的helloWorld插件demo已经跑起来了,相信大家会存在疑问:
- 指令的定位是?(指令是什么、有什么用)
- 指令的触发方式?
什么是指令?
指令在VsCode插件开发中,是指插件行为的集合、入口;要实现什么样的一个功能,需要用户触发的,如点击按钮、输入控制行指令等操作,这些都是指令的应用场景;
指令的触发方式
指令的触发方式常见的分三种,一种是控制台触发,另一种是用户界面点击触发,还有快捷键触发;
使用控制台触发指令,只需要在控制台输入框中输入指令的title
,根据智能提示出来的选项,进行点击或回车确认,即可激活对应的指令(注意:有些插件中会设置指令隐藏或者特定条件触发,了解更多特定条件请戳这里);
// package.json
{
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "helloworld.helloWorld", // 指令id
"title": "Hello World" // 控制台指令名
}
]
},
"menus": {
"commandPalette": [
{
"command": "helloworld.helloWorld",
"when": "explorerResourceIsFolder" // 显示条件,官方叫法是when clause contexts
}
]
}
}
用户界面触发指令,一般是配置界面菜单或者右键菜单选项来指定触发需求的指令;
// package.json
{
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "helloworld.helloWorld",
"title": "Hello World"
}
]
},
"menus": {
"explorer/context": [
{
"command": "helloworld.helloWorld",
"group": "helloworld@1" // 菜单分组,可用作配置二级菜单,@1表示该指令在该菜单下排第一位
}
]
}
}
通过绑定快捷键触发相应的指令;
// package.json
{
"contributes": {
"contributes": {
"commands": [
{
"command": "helloworld.helloWorld",
"title": "Hello World"
}
]
},
"keybindings": [{
// 指定快捷键执行的操作
"command": "helloworld.helloWorld",
// windows下快捷键
"key": "ctrl+f10",
// mac下快捷键
"mac": "cmd+f10",
// 快捷键何时生效
"when": "editorTextFocus"
}]
}
}
插件功能开发
插件功能开发最重要的就是了解指令的注册、指令回调函数绑定、VsCode API应用,通过初始demo的helloWorld
指令演示过程,我们了解到了指令的运行流程,再进一步就是利用VsCode API(所有的VsCode API都可以在node_modules/@types/vscode/index.d.ts
中找到)实现各种功能;
官方插件功能演示仓库中包含各种常见的插件功能代码演示,查阅仓库内的代码有助于了解更多VsCode插件配置和功能;
基础配置
除开插件功能的开发,package.json
的manifest基本配置项如下:
{
"name": "pluginName", // 插件名
"displayName": "pluginName", // 插件市场显示的插件名
"description": "extension description", // 插件市场插件描述
"version": "1.0.0", // 插件版本
"publisher": "pengian", // 插件发布者,打包发布时必填
"engines": {"vscode": "^1.50.0"}, // 限定最小兼容VsCode版本
"categories": ["Other"], // 插件分类,与插件市场有关
"activationEvents": [], // 插件默认失活,定义插件何时激活
"main": "./out/src/extension", // 入口文件
"icon": "logo.png", // 插件市场插件图标
"contributes": {} // 插件指令相关配置项
}
代码片段
在日常开发中,我们总会遇到高频使用的代码片段,为了延长Ctrl C
、Ctrl V
按键的寿命,把这些高频代码段封装成编辑器代码片段十分的必要;
VsCode自带user Snippet
,但这个user Snippet
仅限于一个账户,不利于团队分享,整合成一个代码片段插件更为合理;
以下代码片段(默认指定一个json文件保存代码片段定义如:snippet.json
)示例:
// snippet.json
{
"vue router": { // 快捷触发指令显示的代码片段名,可随意指定,不与其它代码片段重复即可
"prefix": "vrl", // 代码片段快捷触发指令
// body内限定具体的代码片段,一个数组项表示一行,或者使用\n表示换行
// 代码片段中的$具有特殊含义,如代码中含有$,需要转义
// $1、$2、$3...,表示光标占位符,快捷触发指令生成代码片段后,光标立即定位到$1,按Tab切换下一个光标位置
// 可在多个位置同时指定同一个光标占位符,表示多光标
// ${1:a}表示光标位置为1,且默认值为a
// ${1|a,b,c,d,e|}表示光标位置为1,无默认值,但有提示可选值a、b、c、d、e
"body": [
`<router-link tag="${1:a}">$2</router-link>`
],
// 快捷触发指令显示的描述信息
"description": "vue router-link",
// 代码片段作用域,不指定则默认支持所有语言,这里的vue-html是指vue的template模板内有效
"scope": "vue-html"
}
}
以上是代码片段的json文件,想要在插件中实现支持,还得在manifest中配置:
// package.json
{
"contributes": {
"snippets": [
{
"language": "vue", // 限定生效范围
"path": "./snippets.json"
}
]
}
}
悬浮提示
在日常coding的时候,会不会留意到鼠标划过一些特殊的标签、标签属性或者变量方法会出现十分人性的属性提示,在插件中实现这一功能并不难,vscode.languages.registerHoverProvider
这个API提供了这一功能的实现;
import * as vscode from 'vscode';
function activate(context) {
// 指定Hover生效的语言和Hover回调
const disposable = vscode.languages.registerHoverProvider('javascript', {
// doucment方法支持操作、获取编辑器文本,position表示鼠标位置
provideHover(document, position, token) {
// Hover支持markdown常用语法
return new vscode.Hover('I am a hover!');
}
});
context.subscriptions.push(disposable);
}
需要实现element helper
插件的标签、标签属性提示,需要定义方法通过document
和position
来定位当前鼠标悬浮的属性名,再对应写入Hover
提示信息,大致代码如下:
// 文档通过 hover 形式查看
export class DocumentHoverProvider implements HoverProvider {
provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<import('vscode').Hover> {
// 获取当前行文本
const line = document.lineAt(position.line);
// 枚举出标签、标签属性边缘字符的可能性,如<h1>标签的边缘字符为<和>,用作后续结束选中字符拼接
const textSplite = [' ', '<', '>', '"', "'", '.', '\\', '=', ':'];
// 获取当前鼠标悬浮的字符列位置
let posIndex = position.character;
// 截取当前字符列位置的字符,用作后续拼接
let textMeta = line.text.substr(posIndex, 1);
// 初始化选定文本
let selectText = '';
// 前向获取符合要求的字符串,判断字符是否为边缘字符或到达当前行结尾,否则继续拼接选定文本
while (textSplite.indexOf(textMeta) === -1 && posIndex <= line.text.length) {
selectText += textMeta;
textMeta = line.text.substr(++posIndex, 1);
}
// 往后获取符合要求的字符串,判断字符是否为边缘字符或到达当前行行首,否则继续拼接选定文本
posIndex = position.character - 1;
textMeta = line.text.substr(posIndex, 1);
while (textSplite.indexOf(textMeta) === -1 && posIndex > 0) {
selectText = textMeta + selectText;
textMeta = line.text.substr(--posIndex, 1);
}
textMeta = line.text.substr(posIndex, 1);
// tag标签便利
if (Documents[selectText]) {
return new Hover(Documents[selectText]);
}
return null;
}
}
生成文件
VsCode插件的自由度很高,通过vscode.commands.registerCommand
注册指令回调时,有唯一参数uri
:
当从资源管理器中右键执行命令时会把当前选中资源路径uri作为参数传过;
当从编辑器中右键菜单执行时则会将当前打开文件路径URI传过去;
当直接按
Ctrl+Shift+P
执行命令时,这个参数为空;import * as vscode from 'vscode'; function activate(context) { let disposable = vscode.commands.registerCommand('helloworld.helloWorld', (uri) => { vscode.window.showInformationMessage('Hello! Current Folder is ' + uri); }); context.subscriptions.push(disposable); }
获取到当前菜单目录后,使用node来生成相关的文件即可;
实际上除了利用node生成工作区文件外,我们还可以利用node启动一个node server,亦或者访问用户文件等操作;