【實(shí)戰(zhàn)入門】你不知道的 VSCode 插件開發(fā)教程
原本鏈接:https://hellogithub2014.github.io/2019/06/09/vscode-plugin-development/
之前一直以為開發(fā)VS code插件是一件很難的事情,后來工作上需要搞一個效率小工具,就試著找了些資料來入門,發(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)備
這個很簡單,我就直接拷貝過來了。
nodejs: 建議使用 LTS 版本 npm: 建議最新版本 yeoman : npm install -g yo generator-code : npm install -g generator-code
另外小TIPS,我們平時(shí)直接安裝的插件所在目錄是~/.vscode/extensions,有興趣的可以看看這些插件是怎么實(shí)現(xiàn)的。
腳手架
安裝的yo可以直接生成一個Hello World版本的插件目錄。執(zhí)行
yo?code
即會提示一些問題,按照個人喜好填寫即可,最后會生成樣板代碼:
.
├──?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í)就是一個node應(yīng)用,直接按F5即可運(yùn)行。對配置感興趣的也可以查看根目錄下的.vscode/launch.json。
跑起來以后默認(rèn)會新開一個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!
這個就是由插件的真正代碼部分輸出的了。我們接下來看看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()?{
????//?在編輯器右下角展示一個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,
};
以上就是此插件的完整邏輯了,配置注釋是很簡單的。可以看到主要就是兩個生命周期函數(shù),另外搭配一些編輯器api就完成了。
調(diào)試
腳手架已經(jīng)貼心的幫我們加了調(diào)試配置,我們只用添加斷點(diǎn)即可:

Command 配置
上面提到了生成一個command只需要 2 步,先是利用vscode.commands.registerCommand注冊一個,然后再到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)在某個文本中
????}
??],
}
發(fā)布
主要參考的是官方文檔
首先需要安裝vsce工具:
npm?install?-g?vsce
本地打包將插件打包成.vsix文件。
vsce?package
會在項(xiàng)目根目錄生成hello-world-0.0.1.vsix,然后在編輯器的插件面板選擇從VSIX安裝即可:

發(fā)布到插件市場
需要獲取一個token,參考官方文檔 利用token創(chuàng)建一個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映射文件,它是一個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)有多個匹配的代碼片段時(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)出錯會有提示。
插件中使用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類似,提供了普通消息、警告消息、錯誤消息。
vscode.window.showInformationMessage('普通消息');
vscode.window.showWarningMessage('警告消息');
vscode.window.showErrorMessage('錯誤消息');
消息也支持交互按鈕,當(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
在編輯器頂部展示一個input輸入框,使用vscode.window.showInputBox,會返回一個Promise:
const?text:?string?|?undefined?=?await?vscode.window.showInputBox({
??'最后一步,輸入文案'
})

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

每個選項(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 個tab,從左到右依次是問題、輸出、調(diào)試控制臺、終端。output channel就是用于控制輸出 tab的內(nèi)容,可以往其中追加文本、追加行、清空,可以將其看成一個簡單的文件。output channel適用于一次展示大量信息.
使用vscode.window.createOutputChannel創(chuàng)建output channel實(shí)例,然后就可以操作各種api了。
const?opc?=?vscode.window.createOutputChannel('textSearch');?//?可以有多個OutputChannel共存,使用參數(shù)名區(qū)分
opc.clear();?//?清空
opc.appendLine('水電費(fèi)');?//?追加一行
opc.show();?//?打開控制臺并切換到OutputChannel?tab
一個例子:

file selector
有些時(shí)候需要操作本地文件系統(tǒng),例如選擇某個文件、將文件保存到指定位置等。
保存文件到指定位置使用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,?//?是否可以選擇多個文件
??filters:?{
????json:?['json'],?//?文件類型過濾
??},
});
if?(!uris?||?!uris.length)?{
??return;
}
handleFiles(uris);

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

處理hover需要注冊一個hover處理器,vscode會在hover到文本上時(shí)自動調(diào)用處理器,同時(shí)傳遞hover相關(guān)的信息。例如一個展示光標(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的第一個參數(shù)數(shù)組表明此處理器的作用范圍
const?hoverDisposable?=?vscode.languages.registerHoverProvider(['javascript',?'vue'],?{
??provideHover:?hover,
});
context.subscriptions.push(hoverDisposable);
selection
與hover類似,有時(shí)候需要處理選中的文本,獲取它是通過vscode.TextEditor實(shí)例上的屬性,有兩個相關(guān)屬性
selections:所有被選中的文本信息 selection:第一個被選中的文本信息, 等同于selections[0]
獲取TextEditor的一個方法是通過注冊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 種事件,也可以選擇忽略其中某個類型事件。創(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
