作者:HelloGitHub-小夏(首發於 HelloGitHub 公眾號)
作為一個靠程式碼作為“生計”的開發者,bug 寫的好不好,編輯器真的很重要!那麼 Visual Studio Code 這個大名你肯定不會陌生。作為一個老厲害的編輯器,它的過人之處簡單講講來說有這麼幾點:
-
首先,它的設計者是一個很有名的工程師:Eric Gamma。20年前,他是《設計模式:可複用物件導向軟體的基礎》的作者之一,這本書在開發社群的地位被視為物件導向軟體開發的指路明燈(瞻望大佬)。
-
其次,對於寫 JavaScript 的人來說,雖然市面上有很多大大小小不同的編輯器,比如 sublime、atom、webstorm 等等,VS Code 與他們區別在於他比 sublime 開源,比 atom 更快,比 webstorm 更輕。
一、介紹
VS Code 全名 Visual Studio Code 是微軟開源的一款編輯器,GitHub 上標星 115k(11 萬)。它是基於 TypeScript 編寫,總計程式碼數量在 30 萬以上,大型知名開源專案。
我們先來簡單看一下它的產品定位吧~可以看到,專案作者對它的定位屬於輕量級的編輯器,所以要求它輕量、響應速度快,適用於多種語言等等。VS Code 的編輯能力來自於一款同樣來自微軟叫做 monaco 的開源 Web 編輯器,同時為了實現跨平臺的需求又引入了 Electron:一個使用 JavaScript,HTML 和 CSS 構建跨平臺的桌面應用程式。
正因為有了清楚的定位和方向,才會有了更加清晰的邊界。或許你很疑惑,他是怎麼支援多種語言的又做到輕量級的?那我們不得不來看一下它的多程式架構。
- VS Code 有一個主程式入口,負責一些視窗管理、程式間通訊、自動更新等全域性任務;
- 渲染程式,顧名思義負責一個 Web 頁面的渲染;
- 外掛宿主程式,每個外掛的程式碼都執行在一個獨立且隔離的 Node 環境的宿主程式中,外掛無法訪問 UI;
- Debug 程式,用於除錯;
- 語言服務,是一種重要的特殊擴充,可以為許多程式語言提供編輯體驗,還可以實現 VS Code 支援的自動補充,錯誤檢查(診斷),跳轉到定義以及許多其他的語言功能。
最核心的部分就是它的外掛系統,為編輯器的擴充帶來了更加個性化的開源定製。只要你能找到強大的 VS Code 外掛組合在一起,那你的編輯器一定是一個高階且高效率的工作好幫手。
先來看一下,VS Code 大致有哪些可供我們擴充的能力。
有沒有心癢癢地想自己動手搞一個 VS Code 外掛?下面就帶大家做一個入門級的 VS Code 外掛。
二、環境準備
其次「全域性(-g)」安裝 Yeoman(現代 Web 應用程式腳手架工具)和 VS Code Extension Generator 這兩個官方指定的工具腳手架(生成 VS Code 外掛專案的工具)。
npm install -g yo generator-code
當你看到下面的資訊就說明安裝成功了:
三、初始化專案結構
依賴環境搞好了,接下來就要用到 Yeoman 這個工具來幫我們快速建立專案結構啦!可能有很多人對這個腳手架不熟悉,簡單的說,Yeoman 是一個通用的腳手架系統,它允許建立任何型別的應用程式。你可以用它快速開始新專案。所以不僅僅是 JavaScript 這個語言,Java,Python,C#等都可以用它來生成專案,只要有對應的生成器就可以。那我們進行下一步,在命令列中輸入 yo code
。
讓我們來分析一下這幾個選項的意思,其實和字面意思一樣,從上到下:
- 新的外掛(Typescript)
- 新的外掛(JavaScript)
- 新的主題顏色
- 新的語言支援
- 新的程式碼片段
- 新的鍵值繫結
- 新的外掛包
- 新的語言包(本土化)
你可以看到這個工具支援建立多種型別的專案,我們今天先從外掛(Extension)入手,所以第一個和第二個的區別就是,你要是會用 TypeScript 就選第一個,也是官方推薦的一個;要是不想寫各種麻煩的型別定義和型別校驗,就選第二個 JavaScript。
這一次我們選 JavaScript 來做一個簡單的入門, 隨後你會需要填寫一系列初始化的資訊如下:
註釋如下:
- What type of extension do you want to create?(建立哪一種型別的擴充套件?)
- What's the name of your extension?(擴充套件的名稱?應該全部為小寫字母,沒有空格)
- What's the identifier of your extension?(擴充套件的標示?)
- What's the description of your extension?(擴充套件的描述是什麼?)
- Initialize a git repository?(是否初始化 git 倉庫?)
- Which package manager to use? (因為我們裝的是 npm,所以選 npm 就行了,如果你有 yarn,你也可以選 yarn)
- 使用哪一種包管理器(來下載各種 npm 包)
四、搞一個簡單的 VS Code 外掛
前面的準備的差不多啦!那我們就要開始開「綠皮小火車」啦。
進入剛建立的檔案目錄 cd test-extension
,檔案目錄:
或許你覺得檔案目錄嘛,一看就知道了,不就是幾個配置資訊或者專案說明嘛,但是這裡面的配置資訊「非常重要」x3,重要到你可能少一個配置或者配置的不對,功能就出不來。所以我們還是稍微花點筆墨聊聊這裡的資訊。
首先你可以在 package.json
檔案裡面,看到自己在前一個步驟裡面設定的各個值,配置內的各個主要的含義如下,這裡有個小點注意一下,如果你的 VS Code 比較舊,且更新不了最新的,你就把下面的 engines
設定的版本低一點,比如我就改成了 ^1.52.0
確保一定能相容目前的 VS Code 編輯器就可以 :
{
"name": "test-extension", // 外掛的名字
"displayName": "test-extension", // 在外掛市場展示的名字
"description": "vscode extension sample", // 外掛描述
"version": "0.0.1", // 外掛版本
"engines": { // 最低支援 vscode 的版本
"vscode": "^1.52.0"
},
"categories": [ // 外掛的類別,用於在外掛市場做區分
"Other"
],
"activationEvents": [ // 外掛啟用的事件列表,可以有多個觸發機制,所以是陣列形式
"onCommand:test-extension.helloWorld"
],
"main": "./extension.js", // 外掛主入口
"contributes": { // 貢獻點,用於擴充外掛功能的配置項,這裡不會細講,先用 command 舉例
"commands": [
{
"command": "test-extension.helloWorld",
"title": "Hello World"
}
]
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "node ./test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.55.0",
"@types/glob": "^7.1.3",
"@types/mocha": "^8.0.4",
"@types/node": "^12.11.7",
"eslint": "^7.19.0",
"glob": "^7.1.6",
"mocha": "^8.2.1",
"typescript": "^4.1.3",
"vscode-test": "^1.5.0"
}
}
熟悉了配置之後,我們再來看一下外掛的入口檔案 extsnsion.js
,裡面主要有兩個主要的函式,一個是 activate
表示啟用外掛時的處理,一般是註冊命令等一些初始化的操作;另一個是 deactivate
,表示外掛失活的時候做的處理,其實聽名字你也應該有體感,這就是外掛的生命週期裡的兩個鉤子函式嘛。
// 引了 vscode 這個模組,這樣你就可以用它裡面的很多很多功能啦
const vscode = require('vscode');
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('Congratulations, your extension "test-extension" is now active!');
let disposable = vscode.commands.registerCommand('test-extension.helloWorld', function () {
vscode.window.showInformationMessage('Hello World from test-extension!');
});
context.subscriptions.push(disposable);
}
function deactivate() {}
module.exports = {
activate,
deactivate
}
我們來分析一下上面這段程式碼,你可以看到裡面 registerComman
了一個命令,是不是有種似曾相識的感覺?沒錯,就是上面在 package.json
裡面有提到的那個 command
,讓我們摘出來一起看看:
...,
// package.json
"contributes": { // 貢獻點,用於擴充外掛功能的配置項,這裡不會細講,先用 command 舉例
"commands": [
{
"command": "test-extension.helloWorld",
"title": "Hello World"
}
]
},
...
...
// extension.js
function activate(context) {
console.log('Congratulations, your extension "test-extension" is now active!');
let disposable = vscode.commands.registerCommand('test-extension.helloWorld', function () {
vscode.window.showInformationMessage('Hello World from test-extension!');
});
context.subscriptions.push(disposable);
}
...
這樣看起來是不是很直觀了?在 package.json
裡面設定的 command
的值,就是 extension.js
裡面 registerCommand
的值。那這幾行命令是什麼意思呢?讓我們一起來執行看看:
他會幫你開啟一個新的 VS Code 編輯器,外掛只會載入到這個編輯器中。現在我們使用呼叫快捷鍵(MacOS) command+p ,輸入 >Hello World
(不區分大小寫):
回車一下,你會發現在右下角一個不起眼的角落裡輸出了這麼一個提示:
我相信聰明的你們結合程式碼一定已經恍然大悟了對不對!不知道你們有沒有這個疑問,上面那個 console.log
去哪裡看?別急,我們回到外掛程式碼的那個編輯器中,仔細看下面這邊,他會在我們輸入上面的命令之後才出現,因為在 package.json
裡面我們配置外掛的啟用時機就是 onCommand:test-extension.helloWorld
:
那我們現在抱著「刻意學習」的思路,改一下上面的程式碼,比如把 test-extension
改成 test
:
...,
// package.json
"activationEvents": [
"onCommand:test.helloWorld"
],
...,
"contributes": {
"commands": [
{
"command": "test.helloWorld",
"title": "Hello World"
}
]
},
...
// extension.js
...
function activate(context) {
console.log('我在這裡!!');
let disposable = vscode.commands.registerCommand('test.helloWorld', function () {
vscode.window.showInformationMessage('我改變了 command 的名字!');
});
context.subscriptions.push(disposable);
}
...
再按照上面說的觸發方法再來一遍,發現依舊是可以的!所以這裡的名字只是一個名稱空間,你可以改成你想要的任何名字,來適用於比較複雜的外掛體系。既然是個名稱空間,那其實不要這個字首也可以。
五、實現一個屬於自己的外掛
前面介紹了那麼多,大家有沒有發現其實這個體系也不難,有大佬在前面鋪路,其實我們只要按照規則“填空”就好了,那現在我們就來實現一個小小的功能——加一個按鈕和他的點選事件。
修改我們的 package.json
如下,因為當前我希望外掛載入的時候就已經訂閱了按鈕的點選事件,所以這裡我們可以把 activationEvents
改成 *
,這樣的話我們的外掛在一開始就可以啟用並註冊事件了:
...,
"activationEvents": [
"*",
],
"contributes": {
"commands": [
{
"command": "test.helloWorld",
"title": "Hello World"
},
// 註冊一下按鈕點選的事件,再配一個小圖示
{
"command": "test.button",
"title": "按鈕",
"icon": {
"light": "./media/light/preview.svg",
"dark": "./media/dark/preview.svg"
}
}
],
// 在這裡加一下下面這個配置
"menus": {
"editor/title": [
{
"command": "test.button",
"group": "navigation"
}
]
}
},
...
然後回到我們的 extension.js
裡面增加一個簡單的訊息提醒:
function activate(context) {
console.log('我在這裡!!');
let disposable = vscode.commands.registerCommand('test.helloWorld', function () {
vscode.window.showInformationMessage('我改變了 command 的名字!');
});
// 新增一個按鈕的點選命令操作內容
let button = vscode.commands.registerCommand('test.button', function () {
vscode.window.showInformationMessage('按鈕點選');
});
// 記得這個新的命令也要放進去訂閱一下
context.subscriptions.push(disposable, button);
}
看一下效果:
是不是很簡單的就自定義了 VS Code 的樣式?那我們現在就來分析一下我們上面做的事情。首先,我們修改了 package.json
裡的配置,增加了一個 menus
,這個 menus 是什麼呢?答案是選單。選單項定義包含選擇時應呼叫的命令以及該項應顯示的條件(when
),所以你也可以給這個選單項顯示加個顯示的邏輯,比如我們規定在開啟 javascript
檔案時才顯示這個按鈕:
{
"contributes": {
"menus": {
"editor/title": [
{
"when": "resourceLangId == javascript",
"command": "test.button",
"group": "navigation"
}
]
}
}
}
而 group
的含義呢,是用來定義選單項的排序和分組的。來自官網的一個圖,表示不同的 group
之間存在的順序關係,當然這個選單不是上面我們寫的那個,而是 editor/context
,所以不同的選單之間的 group
其實是有細微差別的,但是大體都差不多,而 navigation
的顯示優先順序是最高的:
我們也可以加一個這個看看:
"menus": {
"editor/title": [
{
"command": "test.button",
"group": "navigation",
"when": "resourceLangId == javascript"
}
],
"editor/context": [
{
"command": "test.button",
"group": "navigation",
"when": "resourceLangId == javascript"
}
]
}
效果是一樣的:
如果你好奇還有哪些選單,我這裡簡單整理「翻譯」了一下官網的內容(僅常見選單非全部):
配置選單項的名稱 | 選單位置 |
---|---|
commandPalette | 全域性命令皮膚 |
explorer/context | 資源管理器上下文選單 |
editor/context | 編輯器右鍵上下文選單 |
editor/title | 編輯器標題欄,不配置圖片就顯示文字 |
editor/title/context | 編輯器標題右鍵上下文選單 |
debug/callstack/context | 除錯棧檢視的上下文選單 |
debug/toolbar | 除錯工具欄 |
scm/title | SCM 標題選單 |
view/title | 看標題選單 |
touchBar | macOS 觸控欄 |
timeline/title | 時間軸檢視標題選單欄 |
extension/context | 擴充套件程式檢視上下文選單 |
六、做個總結
從上面的簡單例子可以看出,VS Code 不僅可以支援我們自定義想要的命令,也允許我們在「限定範圍內」對編輯器進行個性化的擴充。為什麼說是限定範圍呢?因為按官網的話來說,目前外掛體系有下面這些侷限性:
外掛不具備訪問 VS Code UI 的 DOM 的能力。所以不能將自定義的 CSS 應用於 VS Code 或將 HTML 元素新增到 VS Code UI 的擴充套件中去。這樣的限制在於:
- 確保使用者的操作在可控範圍內,保證操作的一致性
- 防止因為底層 Web 技術的更迭導致對一些已存在的外掛會有影響
- 保證開發人員可以繼續在原有外掛基礎上進行一如往常的迭代,而不用再去打破原有的規則重新學習
其實我們今天的內容只是工作區擴充很小一部分內容,要學習這個龐大的體系,還是要不斷的努力學習呀!下一次,我們來走進「宣告類語言特性」,想知道編輯器裡的自動提示和補全是怎麼做到的嘛?
關注 HelloGitHub 公眾號 收到第一時間的更新。
還有更多開源專案的介紹和寶藏專案等待你的發掘。