【實(shí)戰(zhàn)入門(mén)】你不知道的 VSCode 插件開(kāi)發(fā)教程
原本鏈接:https://hellogithub2014.github.io/2019/06/09/vscode-plugin-development/
之前一直以為開(kāi)發(fā)VS code插件是一件很難的事情,后來(lái)工作上需要搞一個(gè)效率小工具,就試著找了些資料來(lái)入門(mén),發(fā)現(xiàn)其實(shí)就入門(mén)和開(kāi)發(fā)一些簡(jiǎn)單功能的插件來(lái)說(shuō)難度還是很低的。因?yàn)関scode本身是基于electron開(kāi)發(fā)的,所以總體來(lái)說(shuō)開(kāi)發(fā)插件就是在寫(xiě)node代碼,額外再加一些編輯器api,插件發(fā)布的過(guò)程和npm包的發(fā)布很類(lèi)似。
vscode官方提供的腳手架還幫忙加上了調(diào)試配置,調(diào)試非常方便。下面就來(lái)說(shuō)下具體步驟,在學(xué)習(xí)的過(guò)程中參考了一些博客,放在了最后面。
環(huán)境準(zhǔn)備
這個(gè)很簡(jiǎn)單,我就直接拷貝過(guò)來(lái)了。
-
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
即會(huì)提示一些問(wèn)題,按照個(gè)人喜好填寫(xiě)即可,最后會(huì)生成樣板代碼:
.
├── CHANGELOG.md 插件變更記錄
├── README.md
├── extension.js 插件入口main文件
├── jsconfig.json 編輯器關(guān)于js的配置
├── package.json 全局配置
├── test 測(cè)試代碼文件夾
│ ├── extension.test.js
│ └── index.js
├── vsc-extension-quickstart.md 新手介紹
└── yarn.lock
其中的quickstart.md是新手引導(dǎo),里面包含了對(duì)文件的作用解析、如何運(yùn)行插件、測(cè)試插等等,推薦去看一看,我們?cè)谙旅嬉矔?huì)介紹一些。除此之外在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", // 命令對(duì)應(yīng)的Command,需要和代碼里保持一致
"title": "Hello World" // 命令的顯示名稱(chēng)
}
]
},
"scripts": {
// 正常的npm script
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
},
"devDependencies": {
// 依賴(lài)包
"typescript": "^3.3.1",
"vscode": "^1.1.28",
"eslint": "^5.13.0",
"@types/node": "^10.12.21",
"@types/mocha": "^2.2.42"
}
}
啟動(dòng)、調(diào)試插件
啟動(dòng)運(yùn)行
腳手架生成的其實(shí)就是一個(gè)node應(yīng)用,直接按F5即可運(yùn)行。對(duì)配置感興趣的也可以查看根目錄下的.vscode/launch.json。
跑起來(lái)以后默認(rèn)會(huì)新開(kāi)一個(gè)vscode窗口,然后會(huì)發(fā)現(xiàn)什么都沒(méi)有發(fā)生,這是由插件的啟動(dòng)方式?jīng)Q定的,配置于package.json里的activationEvents項(xiàng)。常用的有:
-
onLanguage在打開(kāi)特定語(yǔ)言類(lèi)型的文件后激活 -
onCommand在執(zhí)行特定命令后激活
由于我們的插件是配置的onCommand啟動(dòng),并且指定的命令名是Hello World,所以我們?cè)谛麻_(kāi)的vscode窗口中按下快捷鍵Command+Shift+P后再找到Hello World,選中并執(zhí)行即可。
最后順利的話,編輯器右下角會(huì)彈出Hello World!。
如果細(xì)心的話,還會(huì)在源窗口的控制臺(tái)的調(diào)試控制臺(tái)tab 中看到如下輸出:
Congratulations, your extension "hello-world" is now active!
這個(gè)就是由插件的真正代碼部分輸出的了。我們接下來(lái)看看extension.js的內(nèi)容:
// vscode編輯器api入口
const vscode = require('vscode');
/**
* 此生命周期方法在插件激活時(shí)執(zhí)行
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
// console的各種方法都是輸出在`調(diào)試控制臺(tái)`tab下
console.log('Congratulations, your extension "hello-world" is now active!');
// registerCommand用于注冊(cè)命令并提供具體邏輯,命令名需要和package.json里寫(xiě)的一致。
// 回調(diào)函數(shù)在命令被觸發(fā)時(shí)執(zhí)行。
let disposable = vscode.commands.registerCommand('extension.helloWorld', function() {
// 在編輯器右下角展示一個(gè)message box
vscode.window.showInformationMessage('Hello World!');
});
// 將registerCommand的返回值放入subscriptions可以自動(dòng)執(zhí)行內(nèi)存回收邏輯。
context.subscriptions.push(disposable);
}
exports.activate = activate;
// 當(dāng)插件被設(shè)置為無(wú)效時(shí)執(zhí)行此生命周期鉤子
function deactivate() {}
module.exports = {
activate,
deactivate,
};
以上就是此插件的完整邏輯了,配置注釋是很簡(jiǎn)單的。可以看到主要就是兩個(gè)生命周期函數(shù),另外搭配一些編輯器api就完成了。
調(diào)試
腳手架已經(jīng)貼心的幫我們加了調(diào)試配置,我們只用添加斷點(diǎn)即可:
Command 配置
上面提到了生成一個(gè)command只需要 2 步,先是利用vscode.commands.registerCommand注冊(cè)一個(gè),然后再到package.json里的contributes.commands中配置即可。圍繞command還可以做一些其他事情,最常見(jiàn)的就是配置右鍵菜單和快捷鍵。
右鍵菜單
表示右鍵的菜單里出現(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
會(huì)在項(xiàng)目根目錄生成hello-world-0.0.1.vsix,然后在編輯器的插件面板選擇從VSIX安裝即可:
發(fā)布到插件市場(chǎng)
-
需要獲取一個(gè)token,參考官方文檔 -
利用token創(chuàng)建一個(gè)publisher,這是在插件市場(chǎng)的用戶
vsce create-publisher (publisher name)
-
本地登錄此用戶
vsce login (publisher name)
-
發(fā)布插件
vsce publish
順利的話在控制臺(tái)會(huì)提示發(fā)布成功,然后過(guò)幾分鐘就可以在插件市場(chǎng)搜到自己的插件啦!?
版本升級(jí)
當(dāng)插件內(nèi)容發(fā)生變更時(shí),重新發(fā)布時(shí)最好更新版本號(hào),vsce可以遵循語(yǔ)義化版本指定升級(jí)大(major)/小(minor)/補(bǔ)丁(patch)版本,也可以直接指定版本號(hào)。例如只升級(jí)小版本:
vsce publish minor
如果插件代碼在gitlab上,因?yàn)閭}(cāng)庫(kù)在內(nèi)網(wǎng),需要事先將README里的圖片替換為公網(wǎng)cdn上的路徑。
snippets
snippets是代碼片段,可以理解為代碼快捷鍵,在輸入很少量觸發(fā)代碼后即可聯(lián)想出一大坨關(guān)聯(lián)代碼,非常方便。對(duì)于js、ts、vue都可以在插件市場(chǎng)找到非常多的snippets插件。
開(kāi)發(fā)snippets只用兩步:
-
編寫(xiě)snippets映射文件,它是一個(gè)json,例如javascript.json:
{
"this$t": {
"prefix": "tt'", // 觸發(fā)代碼
"body": [
// 聯(lián)想出來(lái)的關(guān)聯(lián)代碼
"this.\\$t('${1:key}')" // ${1: key} 是占位符,聯(lián)想出來(lái)后會(huì)自動(dòng)聚焦在這里
],
"description": "this.$t" // snippets描述,當(dāng)有多個(gè)匹配的代碼片段時(shí),可以用來(lái)識(shí)別
}
}
-
在package.json中配置
"contributes": {
"snippets": [
{
"language": "javascript", // 代碼片段起作用的語(yǔ)言類(lèi)型
"path": "./src/snippets/javascript.json" // 對(duì)應(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": "文件類(lèi)型過(guò)濾器"
}
}
},
}
默認(rèn)配置是json schema格式,在覆蓋默認(rèn)配置時(shí)如果校驗(yàn)出錯(cuò)會(huì)有提示。
插件中使用getConfiguration來(lái)讀取配置:
function getConfig() {
const config = vscode.workspace.getConfiguration();
const includes: string[] | undefined = config.get('includes'); // 獲取指定配置項(xiàng)
return {
includes: includes || [],
};
}
監(jiān)聽(tīng)配置項(xiàng)修改
在用戶安裝了插件后,可能會(huì)修改配置,如何實(shí)時(shí)監(jiān)聽(tīng)配置項(xiàng)的修改呢?vscode提供了onDidChangeConfiguration事件監(jiān)聽(tīng)。
vscode.workspace.onDidChangeConfiguration(function(event) {
const configList = ['includes'];
// affectsConfiguration: 判斷是否變更了指定配置項(xiàng)
const affected = configList.some(item => event.affectsConfiguration(item));
if (affected) {
// do some thing ...
}
});
常見(jiàn)編輯器 api
所有vscode相關(guān)api都可以在官網(wǎng)文檔查找,vscode內(nèi)部也集成了.d.ts文件,編輯器內(nèi)直接跳轉(zhuǎn)定義即可。這里只列舉一些常見(jiàn)的api.
-
messgae
用于展示提示性消息,出現(xiàn)在編輯器右下角,而不是頂部或右上角。
和console類(lèi)似,提供了普通消息、警告消息、錯(cuò)誤消息。
vscode.window.showInformationMessage('普通消息');
vscode.window.showWarningMessage('警告消息');
vscode.window.showErrorMessage('錯(cuò)誤消息');
消息也支持交互按鈕,當(dāng)選中按鈕時(shí)返回的是按鈕本身:
vscode.window.showErrorMessage(`與starling的遠(yuǎn)程交互依賴(lài)vscode-starling.sid配置項(xiàng)`, '打開(kāi)配置項(xiàng)').then(selection => {
if (selection === '打開(kāi)配置項(xiàng)') {
vscode.commands.executeCommand('workbench.action.openSettings');
}
});
input box
在編輯器頂部展示一個(gè)input輸入框,使用vscode.window.showInputBox,會(huì)返回一個(gè)Promise:
const text: string | undefined = await vscode.window.showInputBox({
'最后一步,輸入文案'
})
quick pick
用于從一組選項(xiàng)中選擇一個(gè),類(lèi)似于select組件。使用vscode.window.showQuickPick,同樣返回一個(gè)Promise,resolve時(shí)得到被選中的選項(xiàng)或undefined:
const lang: string | undefined = await vscode.window.showQuickPick(['en', 'zh', 'ja'], {
placeHolder: '第一步:選擇語(yǔ)言',
});
每個(gè)選項(xiàng)也可以是對(duì)象類(lèi)型:
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 + ~打開(kāi)控制臺(tái)后,會(huì)出現(xiàn) 4 個(gè)tab,從左到右依次是問(wèn)題、輸出、調(diào)試控制臺(tái)、終端。output channel就是用于控制輸出 tab的內(nèi)容,可以往其中追加文本、追加行、清空,可以將其看成一個(gè)簡(jiǎn)單的文件。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(); // 打開(kāi)控制臺(tái)并切換到OutputChannel tab
一個(gè)例子:
file selector
有些時(shí)候需要操作本地文件系統(tǒng),例如選擇某個(gè)文件、將文件保存到指定位置等。
-
保存文件到指定位置使用showSaveDialog,它會(huì)打開(kāi)文件選擇器彈窗,選擇了保存路徑后點(diǎn)擊確定會(huì)返回選中的路徑,如果點(diǎn)擊取消會(huì)返回undefined。
// 讓用戶手動(dòng)選擇文件的的存儲(chǔ)路徑
const uri = await vscode.window.showSaveDialog({
filters: {
zip: ['zip'], // 文件類(lèi)型過(guò)濾
},
});
if (!uri) {
return false;
}
writeFile(uri.fsPath); // 寫(xiě)入文件
-
文件選擇showOpenDialog同樣會(huì)打開(kāi)文件選擇器彈窗,不過(guò)這次是用于選擇文件,如果有選擇文件會(huì)返回選中的文件路徑,反之返回undefined。
// showOpenDialog返回的是文件路徑數(shù)組
const uris = await window.showOpenDialog({
canSelectFolders: false, // 是否可以選擇文件夾
canSelectMany: false, // 是否可以選擇多個(gè)文件
filters: {
json: ['json'], // 文件類(lèi)型過(guò)濾
},
});
if (!uris || !uris.length) {
return;
}
handleFiles(uris);
hover
有時(shí)候需要在hover到文本上時(shí)展示一些提示信息,例如eslint插件在hover到不合規(guī)的代碼上時(shí)會(huì)展示具體違反了哪些規(guī)則:
處理hover需要注冊(cè)一個(gè)hover處理器,vscode會(huì)在hover到文本上時(shí)自動(dòng)調(diào)用處理器,同時(shí)傳遞hover相關(guān)的信息。例如一個(gè)展示光標(biāo)所在的單詞hover處理器:
/**
* document: 打開(kāi)的文本
* 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)所在單詞的行列號(hà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類(lèi)似,有時(shí)候需要處理選中的文本,獲取它是通過(guò)vscode.TextEditor實(shí)例上的屬性,有兩個(gè)相關(guān)屬性
-
selections:所有被選中的文本信息 -
selection:第一個(gè)被選中的文本信息, 等同于selections[0]
獲取TextEditor的一個(gè)方法是通過(guò)注冊(cè)textEditorCommand,會(huì)在回調(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)聽(tīng)文件是否發(fā)生了變化,可以監(jiān)聽(tīng)到新建、更新、刪除這 3 種事件,也可以選擇忽略其中某個(gè)類(lèi)型事件。創(chuàng)建watcher是利用vscode.workspace.createFileSystemWatcher:
function createFileSystemWatcher(globPattern: GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): FileSystemWatcher;
例如監(jiān)聽(tīng)所有js文件的變動(dòng):
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插件開(kāi)發(fā)全攻略:https://www.cnblogs.com/liuxianan/p/vscode-plugin-overview.html -
VSCode插件開(kāi)發(fā)急速入門(mén):https://juejin.im/entry/6844903640826642440
● 你不知道的 Npm(Node.js 進(jìn)階必備好文)
● 用動(dòng)畫(huà)和實(shí)戰(zhàn)打開(kāi) React Hooks(一):useState 和 useEffect
·END·
匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程
喜歡本文,點(diǎn)個(gè)“在看”告訴我
