在前幾天的美國紐約,微軟舉行了Connect(); //2015大會。通過這次大會,我們可以很高興的看到微軟的確變得更加開放也更加務實了。當然,會上放出了不少新產品和新功能,其中就包括了VS Code的beta版本。而且微軟更進一步,已經在github將VS Code的代碼開源了。除了這些讓人興奮的消息,我們還應該注意到VS Code提供了對拓展的支持。
所以本文就來聊一聊使用TypeScript開發VS Code拓展的話題吧。
本文所使用的拓展的應用商店頁面:
https://marketplace.visualstudio.com/items/JiadongChen.LicenseHeader
github頁面:
https://github.com/chenjd/VSCode-StandardHeader
"萬事開頭,Hello World"。所以我們就從一個Hello World作為起點,開始一步一步構建自己的VScode的拓展。
在開發vscode的拓展之前,我們先要確保電腦上已經安裝了Node.js。之後,我們便可以利用微軟所提供的基於Yeoman的模板生成器來生成vscode拓展的模板了。
npm install -g yo generator-code
之後,我們只需要在終端中運行yo code命令,即可以進入創建拓展模板的引導過程。
yo code
在引導過程中將TypeScript選擇為開發語言,並且填完一些基本信息之後,我們的第一個vscode拓展便創建完成了。
那麼,yo code命令主要為我們做了一些什麼事情呢?
下面便是我們的拓展項目的完整目錄:
. ├── .gitignore //配置不需要加入版本管理的文件 ├── .vscode // VS Code的整合 │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── .vscodeignore //配置不需要加入最終發布到拓展中的文件 ├── README.md ├── src // 源文件 │ └── extension.ts // 如果我們使用js來開發拓展,則該文件的後綴為.js ├── test // test文件夾 │ ├── extension.test.ts // 如果我們使用js來開發拓展,則該文件的後綴為.js │ └── index.ts // 如果我們使用js來開發拓展,則該文件的後綴為.js ├── node_modules │ ├── vscode // vscode對typescript的語言支持。 │ └── typescript // TypeScript的編譯器 ├── out // 編譯之後的輸出文件夾(只有TypeScript需要,JS無需) │ ├── src │ | ├── extension.js │ | └── extension.js.map │ └── test │ ├── extension.test.js │ ├── extension.test.js.map │ ├── index.js │ └── index.js.map ├── package.json // 該拓展的資源配置文件 ├── tsconfig.json // ├── typings // 類型定義文件夾 │ ├── node.d.ts // 和Node.js關聯的類型定義 │ └── vscode-typings.d.ts // 和VS Code關聯的類型定義 └── vsc-extension-quickstart.md
此時,我們只需要點擊在VS Code的Debug區域的Start按鈕即可。
在接下來開啟的一個處於拓展開發模式的新的VS Code中,我們只需要在命令面板中輸入Hello World便可以激活這個模板拓展,彈出一個Hello World的消息彈窗。
那麼,VS Code到底是如何加載並運行拓展的呢?我們又應該如何開發拓展供自己甚至是分享給更多的人更好的使用VS Code呢?
作為拓展項目的資源配置文件,package.json不僅僅描述了該拓展的信息還描述了拓展的功能。需要說明的是,當VS Code啟動時,拓展的package.json文件便會被讀取,並且VS Code還會對package.json文件中的contributes部分的內容做處理。這一點和拓展的源文件extension.ts有所區別。VS Code並不會在啟動時便加載拓展的源代碼。
下面我們就來看一看我們剛剛生成的這個拓展項目中的package.json文件的內容吧。
{ "name": "Standard-Header", "displayName": "Standard-Header", "description": "standard-header", "version": "0.0.1", "publisher": "JiadongChen", "engines": { "vscode": "^0.10.1" }, "categories": [ "Other" ], "activationEvents": [ "onCommand:extension.sayHello" ], "main": "./out/src/extension", "contributes": { "commands": [{ "command": "extension.sayHello", "title": "Hello World" }] }, "scripts": { "vscode:prepublish": "node ./node_modules/vscode/bin/compile", "compile": "node ./node_modules/vscode/bin/compile -watch -p ./" }, "devDependencies": { "typescript": "^1.6.2", "vscode": "0.10.x" } }
我們可以看到,package.json描述了該拓展的一些基本信息,例如拓展的名稱name、拓展的描述信息description以及拓展的版本號version和發布者的信息publisher等等。順帶提一句,直接通過提高版本號的方式就可以使用拓展發布工具來更新已經發布到VS Code應用商店的拓展版本。
除了這些,我們甚至還可以在package.json文件中配置一些拓展在VS Code的應用商店中的展示信息,例如拓展在商店中的種類:categories、拓展的icon圖:icon以及拓展在應用市場主頁的一些展示信息:galleryBanner等等。
... "icon": "res/icon.png", "galleryBanner": { "color": "#5c2d91", "theme": "dark" }, "categories": [ "Other" ], ...
當然,更重要的內容是activationEvents、contributes以及scripts和main這幾項內容。其中scripts項的內容是使用TypeScript進行開發時所特有的,主要的作用是用來提供對ts文件進行編譯的相關信息。而main項則指向了將ts源文件編譯後所生成的js文件。
正如上文所說,VS Code默認狀況下並不會在啟動時立刻執行拓展中的代碼。因此,為了使拓展能夠被激活,我們需要在package.json文件中定義activationEvents項的內容。例如上例中,activationEvents的定義如下:
... "activationEvents": [ "onCommand:extension.sayHello" ], ...
意思是當“onCommand:extension.sayHello”事件被觸發之後,拓展會被激活。那麼在VS Code中,激活事件都有哪些種類呢?下面我們就來歸一下類。
根據文件所使用的語言:onLanguage:${language}
當打開的文件是使用onLanguage所規定的語言時,拓展會被激活。
例如:
... "activationEvents": [ "onLanguage:python" ] ...
根據所輸入的命令:onCommand:${command}
當在onCommand中規定的命令被觸發時,拓展會被激活。在上文生成的Hello World模板中,我們就可以看到。
... "activationEvents": [ "onCommand:extension.sayHello" ] ...
根據文件夾:workspaceContains:${toplevelfilename}
"activationEvents": [ "workspaceContains:package.json" ],
無限制:*
... "activationEvents": [ "*" ] ...
由於沒有限制,因此在VS Code一啟動時便會激活該拓展。因此,這種不加以條件限制就激活拓展的方式並不是十分推薦的。大家最好謹慎使用。
在package.json文件中的contributes項中,同樣包含很多種類。如果需要歸類的話,可以分為以下幾個類型。
但是本文限於篇幅,將只關注commands和keybindings這兩種。
commands
還以上文中的Hello World為例,我們在commands項中定義了命令的title以及title所對應的具體命令。如下所示:
"contributes": { "commands": [{ "command": "extension.sayHello", "title": "Hello World" }] },
此時一旦安裝了該拓展,我們就可以在VS Code中的命令面板(我在Mac上的快捷鍵是Shift+cmd+p)中找到“Hello World”,輸入便會觸發extension.sayHello這條命令,而上文中的拓展激活事件項activationEvents中如果定義的內容是"onCommand:extension.sayHello",則激活事件被觸發,拓展的代碼被激活。
keybindings
當然,和commands對應的,我們還可以通過在package.json中定義keybindings項,通過綁定快捷鍵的方式觸發命令。這樣操作起來更加便捷,例如下文中我們自己開發一個插入標准頁眉的拓展時,使用的便是綁定快捷鍵的方式。
"contributes": { "keybindings": [{ "command": "extension.insertHeader", "key": "shift+cmd+1", "when": "editorTextFocus" }] },
如果我們在VS Code中安裝了定義了快捷鍵觸發的拓展,則打開VS Code的KeyBoard ShortCut就可以看到拓展中定義的快捷鍵和它對應的命令了。
通過上一小節的內容,我們就能很清楚的理解上文中那個模板拓展的運行過程了。
VS Code首先會檢測到拓展並且讀取拓展的package.json文件的內容並將package.json文件中的contributes項應用到VS Code中。這樣,我們就可以根據contributes項中的內容,或者是在命令面板中輸入contributes項中定義的commands,或者是使用contributes項中所定義的快捷鍵keybindings來觸發extension.sayHello命令。
一旦extension.sayHello觸發,VS Code會創建一個叫做onCommand:extension.sayHello的激活事件。
與此同時,所有在自己的package.json文件中將activationEvents設置為onCommand:extension.sayHello的拓展會被激活。並且會根據package.json文件中main項的內容,將./out/src/extension.js文件加載到JavaScript VM中。接下來,VS Code會調用在extension.js文件中的active函數,在active函數中,我們需要提供“extension.sayHello”這個函數的具體實現並執行。
好了,了解了VS Code拓展的基本知識之後我們就可以開始著手開發自己的拓展了,這個拓展的目的是向源文件添加License頁眉。
既然要打造自己的VS Code,那麼自己喜歡的一些操作習慣自然希望能夠用在VS Code上。而插入頁眉便是這樣一個簡單的小功能。
我們首先來看一看在上文中那個Hello World模板拓展的源文件即extension.ts文件的基本內容。
import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext) { var disposable = vscode.commands.registerCommand('extension.sayHello', () => { ...
vscode.window.showInformationMessage('Hello World!');
...
}); }
首先,我們自然要引入vscode命名空間下的API,以獲得控制VS Code的能力。
其次,我們注意到了拓展的源文件需要export一個叫做activate的函數,當拓展的package.json文件中activationEvents所定義的事件被觸發時,VS Code會調用activate函數。
最後,我們可以看到我們使用vscode的API注冊了一個叫做“extension.sayHello”的命令,並且提供了它的具體實現,按照模板中的邏輯,“extension.sayHello”命令的邏輯是在窗口中顯示一個內容為“Hello World”的信息消息。具體的效果在本文一開始的部分各位已經看到了。
那麼接下來就十分簡單了,我們首先新建一個叫做HeaderGenerator的類,用來執行插入頁眉的任務。
class HeaderGenerator { private _disposable: Disposable; public insertHeader() { // Get the current text editor let editor = window.activeTextEditor; if (!editor) { return; } // Define header content var header = "License Header"; // Get the document var doc = editor.document; // Insert header editor.edit((eb) => { eb.insert(doc.positionAt(0), header); }); } dispose() { this._disposable.dispose(); } }
插入頁眉的具體執行是當前窗口的editor(TextEditor類)的edit方法,通過利用TextEditorEdit類的insert方法實現對當前的文檔doc(TextDocument類)插入新的內容。
之後,我們只需在extension.ts文件中的activate函數中實現調用HeaderGenerator類的insertHeader方法即可。
import * as vscode from 'vscode'; import {window, commands, Disposable, ExtensionContext, TextDocument} from 'vscode'; export function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "standard-header" is now active!'); let headerGen = new HeaderGenerator(); var disposable = vscode.commands.registerCommand('extension.insertHeader', () => { headerGen.insertHeader(); }); context.subscriptions.push(headerGen); context.subscriptions.push(disposable); }
當然,為了增加快捷鍵“shift+cmd+1”的支持,我們還需要在package.json中修改contributes的內容:
"contributes": { "keybindings": [{ "command": "extension.insertHeader", "key": "shift+cmd+1", "when": "editorTextFocus" }] },
之後,讓我們先進入拓展開發模式看一下效果。使用快捷鍵shift+cmd+1,我們可以看到在VS Code的窗口中插入了頁眉(截圖中的頁眉內容我已經換成了MIT協議)。
好了,到此我們的小小的在源文件中插入頁眉的拓展就完成了,下面就讓我們來使用安裝、甚至是發布到VS Code的應用商店讓大家一起分享我們的勞動成果吧。
為了不必再每次都要進入拓展開發模式才能“使用”我們的拓展,我們就必須讓自己的VS Code安裝這個拓展。
事實上在本地安裝供自己的拓展是十分簡單並且方便的事情。VS Code會在.vscode/extensions文件夾中獲取本地的拓展。而.vscode/extensions文件夾所在的位置我們可以總結如下:
我們只需拷貝一份我們的拓展到.vscode/extensions文件夾中,VS Code在啟動時便能夠檢查到該拓展了。
當然,除了自己使用自己開發的拓展之外,我們還可以將拓展發布到VS Code的應用商店。此時我們就要借助一個拓展發布工具——vsce了。
首先是安裝vsce:
npm install -g vsce
之後我們還需要到Visual Studio Team Services注冊並登陸,以獲取Personal Access Token。
一旦獲取了Personal Access Token,我們就可以使用vsce創建一個發布者賬號了。
vsce create-publisher
緊接著,登陸並發布:
vsce login vsce publish
發布成功之後,我們就可以在VS Code的應用商店中看到自己的拓展了。
並且在VS Code的命令面板中,我們也可以使用命令安裝:
ext install
好啦,行文至此,使用TypeScript拓展你自己的VS Code講的已經差不多了。希望各位多多交流~