VS Code 插件開發(fā)入門教程
原本鏈接:https://hellogithub2014.github.io/2019/06/09/vscode-plugin-development/
之前一直以為開發(fā)VS code插件是一件很難的事情,后來工作上需要搞一個(gè)效率小工具,就試著找了些資料來入門,發(fā)現(xiàn)其實(shí)就入門和開發(fā)一些簡單功能的插件來說難度還是很低的。因?yàn)関scode本身是基于electron開發(fā)的,所以總體來說開發(fā)插件就是在寫node代碼,額外再加一些編輯器api,插件發(fā)布的過程和npm包的發(fā)布很類似。
vscode官方提供的腳手架還幫忙加上了調(diào)試配置,調(diào)試非常方便。下面就來說下具體步驟,在學(xué)習(xí)的過程中參考了一些博客,放在了最后面。
環(huán)境準(zhǔn)備
這個(gè)很簡單,我就直接拷貝過來了。
nodejs: 建議使用 LTS 版本 npm: 建議最新版本 yeoman : npm install -g yo generator-code : npm install -g generator-code
另外小TIPS,我們平時(shí)直接安裝的插件所在目錄是~/.vscode/extensions,有興趣的可以看看這些插件是怎么實(shí)現(xiàn)的。
腳手架
安裝的yo可以直接生成一個(gè)Hello World版本的插件目錄。執(zhí)行
yo code
即會提示一些問題,按照個(gè)人喜好填寫即可,最后會生成樣板代碼:
.
├── CHANGELOG.md 插件變更記錄
├── README.md
├── extension.js 插件入口main文件
├── jsconfig.json 編輯器關(guān)于js的配置
├── package.json 全局配置
├── test 測試代碼文件夾
│ ├── extension.test.js
│ └── index.js
├── vsc-extension-quickstart.md 新手介紹
└── yarn.lock
其中的quickstart.md是新手引導(dǎo),里面包含了對文件的作用解析、如何運(yùn)行插件、測試插等等,推薦去看一看,我們在下面也會介紹一些。除此之外在package.json里也包含了很多非常重要的信息:
{
"name": "hello-world", // 插件名
"displayName": "hello-world",
"description": "hello world", // 插件描述
"version": "0.0.1",
"engines": {
"vscode": "^1.35.0" // 運(yùn)行插件需要vscode最低版本
},
"categories": ["Other"],
"activationEvents": ["onCommand:extension.helloWorld"], // 如何激活插件:在命令面板(Command+Shift+P吊起)輸入helloWorld. 注意command名需要在contributes.commands中有配置
"main": "./extension.js", // 插件入口
"contributes": {
"commands": [
// 此數(shù)組表示插件支持的所有命令
{
"command": "extension.helloWorld", // 命令對應(yīng)的Command,需要和代碼里保持一致
"title": "Hello World" // 命令的顯示名稱
}
]
},
"scripts": {
// 正常的npm script
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
},
"devDependencies": {
// 依賴包
"typescript": "^3.3.1",
"vscode": "^1.1.28",
"eslint": "^5.13.0",
"@types/node": "^10.12.21",
"@types/mocha": "^2.2.42"
}
}
啟動、調(diào)試插件
啟動運(yùn)行
腳手架生成的其實(shí)就是一個(gè)node應(yīng)用,直接按F5即可運(yùn)行。對配置感興趣的也可以查看根目錄下的.vscode/launch.json。
跑起來以后默認(rèn)會新開一個(gè)vscode窗口,然后會發(fā)現(xiàn)什么都沒有發(fā)生,這是由插件的啟動方式?jīng)Q定的,配置于package.json里的activationEvents項(xiàng)。常用的有:
onLanguage在打開特定語言類型的文件后激活onCommand在執(zhí)行特定命令后激活
由于我們的插件是配置的onCommand啟動,并且指定的命令名是Hello World,所以我們在新開的vscode窗口中按下快捷鍵Command+Shift+P后再找到Hello World,選中并執(zhí)行即可。

最后順利的話,編輯器右下角會彈出Hello World!。

如果細(xì)心的話,還會在源窗口的控制臺的調(diào)試控制臺tab 中看到如下輸出:
Congratulations, your extension "hello-world" is now active!
這個(gè)就是由插件的真正代碼部分輸出的了。我們接下來看看extension.js的內(nèi)容:
// vscode編輯器api入口
const vscode = require('vscode');
/**
* 此生命周期方法在插件激活時(shí)執(zhí)行
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
// console的各種方法都是輸出在`調(diào)試控制臺`tab下
console.log('Congratulations, your extension "hello-world" is now active!');
// registerCommand用于注冊命令并提供具體邏輯,命令名需要和package.json里寫的一致。
// 回調(diào)函數(shù)在命令被觸發(fā)時(shí)執(zhí)行。
let disposable = vscode.commands.registerCommand('extension.helloWorld', function() {
// 在編輯器右下角展示一個(gè)message box
vscode.window.showInformationMessage('Hello World!');
});
// 將registerCommand的返回值放入subscriptions可以自動執(zhí)行內(nèi)存回收邏輯。
context.subscriptions.push(disposable);
}
exports.activate = activate;
// 當(dāng)插件被設(shè)置為無效時(shí)執(zhí)行此生命周期鉤子
function deactivate() {}
module.exports = {
activate,
deactivate,
};
以上就是此插件的完整邏輯了,配置注釋是很簡單的。可以看到主要就是兩個(gè)生命周期函數(shù),另外搭配一些編輯器api就完成了。
調(diào)試
腳手架已經(jīng)貼心的幫我們加了調(diào)試配置,我們只用添加斷點(diǎn)即可:

Command 配置
上面提到了生成一個(gè)command只需要 2 步,先是利用vscode.commands.registerCommand注冊一個(gè),然后再到package.json里的contributes.commands中配置即可。圍繞command還可以做一些其他事情,最常見的就是配置右鍵菜單和快捷鍵。
右鍵菜單
表示右鍵的菜單里出現(xiàn)指定command,配置方法:
"contributes":{
"menus": {
"editor/context": [
{
"when": "editorHasSelection && resourceFilename =~ /.js|.vue|.ts/", // 出現(xiàn)時(shí)機(jī),當(dāng)編輯器中有選中文本同時(shí)文件名后綴是js/vue/ts
"command": "extension.starling_textSearch", // 需要在`contributes.commands`存在此命令
"group": "6_Starling" // 命令所在的組,右鍵菜單可以分組,組與組之間存在分隔線
},
]
}
}

快捷鍵
有了快捷鍵后,就不用每次在命令面板里查找并運(yùn)行命令了,同樣是在package.json中配置:
"contributes": {
"keybindings": [
{
"command": "extension.starling_textSearch",
"key": "ctrl+f11", // 在Windows上的快捷鍵
"mac": "cmd+f11", // 在mac上的快捷鍵
"when": "editorTextFocus" // 出現(xiàn)時(shí)機(jī), 當(dāng)編輯器焦點(diǎn)在某個(gè)文本中
}
],
}
發(fā)布
主要參考的是官方文檔
首先需要安裝vsce工具:
npm install -g vsce
本地打包將插件打包成.vsix文件。
vsce package
會在項(xiàng)目根目錄生成hello-world-0.0.1.vsix,然后在編輯器的插件面板選擇從VSIX安裝即可:

發(fā)布到插件市場
需要獲取一個(gè)token,參考官方文檔 利用token創(chuàng)建一個(gè)publisher,這是在插件市場的用戶
vsce create-publisher (publisher name)
本地登錄此用戶
vsce login (publisher name)
發(fā)布插件
vsce publish
順利的話在控制臺會提示發(fā)布成功,然后過幾分鐘就可以在插件市場搜到自己的插件啦!??
版本升級
當(dāng)插件內(nèi)容發(fā)生變更時(shí),重新發(fā)布時(shí)最好更新版本號,vsce可以遵循語義化版本指定升級大(major)/小(minor)/補(bǔ)丁(patch)版本,也可以直接指定版本號。例如只升級小版本:
vsce publish minor
如果插件代碼在gitlab上,因?yàn)閭}庫在內(nèi)網(wǎng),需要事先將README里的圖片替換為公網(wǎng)cdn上的路徑。
snippets
snippets是代碼片段,可以理解為代碼快捷鍵,在輸入很少量觸發(fā)代碼后即可聯(lián)想出一大坨關(guān)聯(lián)代碼,非常方便。對于js、ts、vue都可以在插件市場找到非常多的snippets插件。
開發(fā)snippets只用兩步:
編寫snippets映射文件,它是一個(gè)json,例如javascript.json:
{
"this$t": {
"prefix": "tt'", // 觸發(fā)代碼
"body": [
// 聯(lián)想出來的關(guān)聯(lián)代碼
"this.\\$t('${1:key}')" // ${1: key} 是占位符,聯(lián)想出來后會自動聚焦在這里
],
"description": "this.$t" // snippets描述,當(dāng)有多個(gè)匹配的代碼片段時(shí),可以用來識別
}
}
在package.json中配置
"contributes": {
"snippets": [
{
"language": "javascript", // 代碼片段起作用的語言類型
"path": "./src/snippets/javascript.json" // 對應(yīng)的映射文件
}
]
}
最后就可以在編輯器看到效果了:

更多細(xì)節(jié)參考snippets-syntax
插件默認(rèn)配置
很多插件是需要一些額外配置才能工作的,設(shè)置默認(rèn)配置同樣在package.json里:
"contributes": {
"configuration": { // 默認(rèn)配置
"type": "object",
"title": "",
"required": [
"sid"
],
"properties": {
"includes": {
"type": "Array",
"default": [
"json"
],
"description": "文件類型過濾器"
}
}
},
}
默認(rèn)配置是json schema格式,在覆蓋默認(rèn)配置時(shí)如果校驗(yàn)出錯(cuò)會有提示。
插件中使用getConfiguration來讀取配置:
function getConfig() {
const config = vscode.workspace.getConfiguration();
const includes: string[] | undefined = config.get('includes'); // 獲取指定配置項(xiàng)
return {
includes: includes || [],
};
}
監(jiān)聽配置項(xiàng)修改
在用戶安裝了插件后,可能會修改配置,如何實(shí)時(shí)監(jiān)聽配置項(xiàng)的修改呢?vscode提供了onDidChangeConfiguration事件監(jiān)聽。
vscode.workspace.onDidChangeConfiguration(function(event) {
const configList = ['includes'];
// affectsConfiguration: 判斷是否變更了指定配置項(xiàng)
const affected = configList.some(item => event.affectsConfiguration(item));
if (affected) {
// do some thing ...
}
});
常見編輯器 api
所有vscode相關(guān)api都可以在官網(wǎng)文檔查找,vscode內(nèi)部也集成了.d.ts文件,編輯器內(nèi)直接跳轉(zhuǎn)定義即可。這里只列舉一些常見的api.
messgae
用于展示提示性消息,出現(xiàn)在編輯器右下角,而不是頂部或右上角。
和console類似,提供了普通消息、警告消息、錯(cuò)誤消息。
vscode.window.showInformationMessage('普通消息');
vscode.window.showWarningMessage('警告消息');
vscode.window.showErrorMessage('錯(cuò)誤消息');
消息也支持交互按鈕,當(dāng)選中按鈕時(shí)返回的是按鈕本身:
vscode.window.showErrorMessage(`與starling的遠(yuǎn)程交互依賴vscode-starling.sid配置項(xiàng)`, '打開配置項(xiàng)').then(selection => {
if (selection === '打開配置項(xiàng)') {
vscode.commands.executeCommand('workbench.action.openSettings');
}
});

input box
在編輯器頂部展示一個(gè)input輸入框,使用vscode.window.showInputBox,會返回一個(gè)Promise:
const text: string | undefined = await vscode.window.showInputBox({
'最后一步,輸入文案'
})

quick pick
用于從一組選項(xiàng)中選擇一個(gè),類似于select組件。使用vscode.window.showQuickPick,同樣返回一個(gè)Promise,resolve時(shí)得到被選中的選項(xiàng)或undefined:
const lang: string | undefined = await vscode.window.showQuickPick(['en', 'zh', 'ja'], {
placeHolder: '第一步:選擇語言',
});

每個(gè)選項(xiàng)也可以是對象類型:
const option: Object | undefined = await vscode.window.showQuickPick([{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }], {
placeHolder: 'select an option',
});
output channel
在利用Control + ~打開控制臺后,會出現(xiàn) 4 個(gè)tab,從左到右依次是問題、輸出、調(diào)試控制臺、終端。output channel就是用于控制輸出 tab的內(nèi)容,可以往其中追加文本、追加行、清空,可以將其看成一個(gè)簡單的文件。output channel適用于一次展示大量信息.
使用vscode.window.createOutputChannel創(chuàng)建output channel實(shí)例,然后就可以操作各種api了。
const opc = vscode.window.createOutputChannel('textSearch'); // 可以有多個(gè)OutputChannel共存,使用參數(shù)名區(qū)分
opc.clear(); // 清空
opc.appendLine('水電費(fèi)'); // 追加一行
opc.show(); // 打開控制臺并切換到OutputChannel tab
一個(gè)例子:

file selector
有些時(shí)候需要操作本地文件系統(tǒng),例如選擇某個(gè)文件、將文件保存到指定位置等。
保存文件到指定位置使用showSaveDialog,它會打開文件選擇器彈窗,選擇了保存路徑后點(diǎn)擊確定會返回選中的路徑,如果點(diǎn)擊取消會返回undefined。
// 讓用戶手動選擇文件的的存儲路徑
const uri = await vscode.window.showSaveDialog({
filters: {
zip: ['zip'], // 文件類型過濾
},
});
if (!uri) {
return false;
}
writeFile(uri.fsPath); // 寫入文件
文件選擇showOpenDialog同樣會打開文件選擇器彈窗,不過這次是用于選擇文件,如果有選擇文件會返回選中的文件路徑,反之返回undefined。
// showOpenDialog返回的是文件路徑數(shù)組
const uris = await window.showOpenDialog({
canSelectFolders: false, // 是否可以選擇文件夾
canSelectMany: false, // 是否可以選擇多個(gè)文件
filters: {
json: ['json'], // 文件類型過濾
},
});
if (!uris || !uris.length) {
return;
}
handleFiles(uris);

hover
有時(shí)候需要在hover到文本上時(shí)展示一些提示信息,例如eslint插件在hover到不合規(guī)的代碼上時(shí)會展示具體違反了哪些規(guī)則:

處理hover需要注冊一個(gè)hover處理器,vscode會在hover到文本上時(shí)自動調(diào)用處理器,同時(shí)傳遞hover相關(guān)的信息。例如一個(gè)展示光標(biāo)所在的單詞hover處理器:
/**
* document: 打開的文本
* position:hover的位置
* token: 用于取消hover處理器作用
*/
async function hover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) {
const line = document.lineAt(position).text; // 光標(biāo)所在的行
// getWordRangeAtPosition獲取光標(biāo)所在單詞的行列號范圍;getText獲取指定范圍的文本
const positionWord = document.getText(document.getWordRangeAtPosition(position));
console.log('光標(biāo)所在位置的單詞是:', positionWord);
}
// registerHoverProvider的第一個(gè)參數(shù)數(shù)組表明此處理器的作用范圍
const hoverDisposable = vscode.languages.registerHoverProvider(['javascript', 'vue'], {
provideHover: hover,
});
context.subscriptions.push(hoverDisposable);
selection
與hover類似,有時(shí)候需要處理選中的文本,獲取它是通過vscode.TextEditor實(shí)例上的屬性,有兩個(gè)相關(guān)屬性
selections:所有被選中的文本信息 selection:第一個(gè)被選中的文本信息, 等同于selections[0]
獲取TextEditor的一個(gè)方法是通過注冊textEditorCommand,會在回調(diào)函數(shù)里提供TextEditor實(shí)例,例如展示選中文本:
let command = vscode.commands.registerTextEditorCommand('extension.selection', function(textEditor, edit) {
const text = textEditor.document.getText(textEditor.selection);
console.log('選中的文本是:', text);
});
context.subscriptions.push(command);
FileSystemWatcher
用于監(jiān)聽文件是否發(fā)生了變化,可以監(jiān)聽到新建、更新、刪除這 3 種事件,也可以選擇忽略其中某個(gè)類型事件。創(chuàng)建watcher是利用vscode.workspace.createFileSystemWatcher:
function createFileSystemWatcher(globPattern: GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): FileSystemWatcher;
例如監(jiān)聽所有js文件的變動:
const watcher = vscode.workspace.createFileSystemWatcher('*.js', false, false, false);
watcher.onDidChange(e => { // 文件發(fā)生更新
console.log('js changed,' e.fsPath);
});
watcher.onDidCreate(e => { // 新建了js文件
console.log('js created,' e.fsPath);
});
watcher.onDidDelete(e => { // 刪除了js文件
console.log('js deleted,' e.fsPath);
});
參考文章
https://code.visualstudio.com/api VSCode插件開發(fā)全攻略:https://www.cnblogs.com/liuxianan/p/vscode-plugin-overview.html VSCode插件開發(fā)急速入門:https://juejin.im/entry/6844903640826642440
