跳到主要內容
黯羽輕揚每天積累一點點

外掛程式機制詳述_VSCode 外掛程式開發筆記 1

免費2018-02-03#Tool#VSCode插件教程#VSCode extension tutorial#VSCode插件API#VSCode extension API

終於把 Sublime Text 移出了 Dock,開始慢慢喜歡 VS Code

一.擴充能力

VS Code 外掛程式不適合做 UI 客製化,例如 Atom 的 tool-bar 在 VS Code 很難實作:

atom-tool-bar

提供了豐富的擴充能力模型,但不允許外掛程式直接訪問底層 UI DOM(也就是說外掛程式難以改變 IDE 外觀,UI 客製化受限),這是出於方便底層持續最佳化考慮:

With VS Code, we're continually trying to optimize use of the underlying web technologies to deliver an always available, highly responsive editor and we will continue to tune our use of the DOM as these technologies and our product evolve.

UI DOM 這一層可能會隨著最佳化頻繁變動,VS Code 不希望這些最佳化項受限於外掛程式依賴,所以乾脆把 UI 客製化能力限制起來。

除 UI 客製化之外的,IDE 相關的功能型特性都是支援擴充的,如基礎的語法高亮/API 提示、引用跳轉(轉到定義)/檔案搜尋、主題客製化、高級的 debug 協定等等。

P.S.實際上,非要擴充 UI,也是有辦法的(逃出外掛程式執行環境,但要費不少力氣),具體見 access electron API from vscode extension,後續筆記會詳細介紹。

二.執行環境

為了效能與相容性,外掛程式在獨立的行程(稱為 extension host process)中執行,並且不允許直接訪問 DOM,所以提供了一套內建的 UI 元件,比如智慧提示(IntelliSense)。

所以外掛程式崩潰或無回應不影響 IDE 正常執行,例如:

// ref: my-extension/src/extension.ts
export function activate(context: vscode.ExtensionContext) {
  // hang up
  while (true);
}

一個外掛程式的無窮迴圈並不影響 IDE 的正常使用和其它外掛程式的載入/啟動,但在行程列表能夠看到 Code Helper 的 CPU 佔用接近 100%,行程級沙箱保證了外掛程式機制的穩定性。

三.核心理念

穩定性:外掛程式隔離

外掛程式可能會影響啟動效能和 IDE 自身的穩定性,所以通過行程隔離來解決這個問題,外掛程式執行在獨立的行程中,不影響 IDE 及其啟動時間。

這樣做是從使用者角度考慮的,希望使用者對 IDE 擁有完全的控制力,無論外掛程式在做什麼,都不影響 IDE 基本功能的正常使用。

P.S.extension host process 是個特殊的 Node 行程,能夠訪問 VS Code 擴充 API,VS Code 也對這種行程提供了 debug 支援。

效能:外掛程式啟動

外掛程式都是懶載入的(as late as possible),只在特定場景才載入/啟動,所有在此之前也不耗費記憶體等資源。

實作上是外掛程式註冊特定啟動事件(activation events),由 IDE 來觸發執行,比如 markdown 外掛程式只在使用者開啟 md 檔案時才需要啟動。

啟動方式

外掛程式有 6 種啟動方式:

onLanguage:${language} 開啟特定語言的文件
onCommand:${command} 通過 Command Palette 執行特定命令
onDebug 進入除錯模式
workspaceContains:${toplevelfilename} 開啟的資料夾裡含有特定檔案
onView:${viewId} 展開指定 view
* 開啟 IDE 就啟動

"activationEvents": ["*"]外都是條件啟動,只在特定場景或滿足特定條件時才載入/啟動外掛程式。

外掛程式清單檔案

清單檔案用來描述外掛程式的 meta 資訊,直接把package.json作為清單檔案,並增加了一些特有欄位,比如觸發外掛程式載入的啟動事件(activation events)、外掛程式想要增強的擴充點(contribution points)。

IDE 在啟動過程中掃一遍外掛程式清單檔案,UI 相關的就擴充 UI,UI 無關的就把擴充點與外掛程式功能關聯起來。

另外,由於外掛程式的執行環境是 Node 行程,所以 npm package 都是可用的,依賴模組同樣宣告在package.json裡。注意,使用者安裝外掛程式時不會自動npm install,所以需要在發布外掛程式前把依賴模組打包進去,具體見 Installation and Packaging

P.S.擴充點類似於 AOP 裡的 Join point(連接點),即「允許在這裡擴充/增強」,比如新增一個自定義命令,就是對commands擴充點的增強。

manifest

// package.json
{
  // 外掛程式名稱
  "name": "my-extension",
  // 顯示名稱
  "displayName": "MyExtension",
  // 描述資訊
  "description": "An awesome vscode  extension",
  // 版本號 semver 格式
  "version": "0.0.1",
  // 在外掛程式市場展示的圖示
  "icon": "img/icon.png",
  // 發布者名字
  "publisher": "ayqy",
  // vscode 版本要求
  "engines": {
    "vscode": "^1.19.0"
  },
  // 所屬分類,可選 Languages, Snippets, Linters, Themes 等等
  "categories": ["Other"],
  // 載入/啟動方式
  "activationEvents": ["onLanguage:javascript"],
  // 入口檔案路徑
  "main": "./out/extension",
  // 註冊擴充點關聯
  "contributes": {
    "languages": [
      {
        "id": "javascript",
        "aliases": ["JavaScript", "javascript"],
        "extensions": [".js"]
      }
    ]
  }
}

P.S.完整的見 Extension Manifest File - package.json

extension.ts/activate只觸發一次,根據package.json聲明的activationEvents來觸發,觸發條件可以是開啟特定語言的檔案,或者執行特定命令。啟動之後,直到 IDE 被关闭/崩潰才會觸發extension.ts/deactivate,所以一般用法是:

  • activate: 外掛程式被啟動,初始化功能模組單例(只執行一次)

  • deactivate: IDE 即將關閉,清理現場,但不宜做太耗時的動作,因為據說 最多只等待 10s

擴充點

即支援的擴充類型,都宣告在package.json/contributes下,包括:

configuration 外掛程式設定項,使用者可以通過 Settings 設定
configurationDefaults 外掛程式設定項預設值
commands  新增命令,使用者可以通過 Command Palette 輸入特定命令啟動外掛程式功能
menus     新增與命令關聯的選單項,使用者點選選單項時執行對應命令
keybindings 新增與命令關聯的快速鍵,使用者按下特定快速鍵時執行對應命令
languages 與檔案類型建立關聯或擴充新語言,使用者開啟(滿足某些要求的)特定檔案類型時執行對應命令
debuggers 新增 debugger,通過 VS Code debug 協定與 IDE 通訊
breakpoints 配合 debuggers,宣告對 debugger 支援的(程式)語言類型
grammars 新增 TextMate 語法描述,語法高亮
themes 新增客製化主題
snippets 新增程式碼片段
jsonValidation 新增 json 格式校驗
problemMatchers 新增錯誤匹配,從 lint 結果解析出 error, warning 等
problemPatterns 配合 problemMatchers,定義匹配模式
views 新增左側檔案檢視器視圖和除錯視圖分欄

menus唯一的 UI 擴充官方途徑,支援擴充的選單具體如下:

Command Palette 搜尋框下方選單 commandPalette
檔案檢視器右鍵選單 explorer/context
編輯器
  右鍵選單 editor/context
  標題列選單 editor/title
  標題列右鍵選單 editor/title/context
除錯視圖
  呼叫堆疊右鍵選單 debug/callstack/context
SCM(原始碼管理)視圖
  標題列選單 scm/title
  檔案分組選單 scm/resourceGroup/context
  檔案狀態選單 scm/resource/context
  檔案變動選單 scm/change/title
左側視圖
  檔案檢視器分欄 view/title
  除錯視圖分欄 view/item/context

P.S.都是些不起眼的位置,大刀闊斧的 UI 客製化是不支援的,比如想在左端側邊欄(Activity Bar)加個 Icon 都是做不到的。

標題列上的選單擴充支援自定義 icon,但定義方式比較奇怪,例如:

"commands": [{
  "command": "markdown.showPreviewToSide",
  "title": "%markdown.previewSide.title%",
  "category": "Markdown",
  "icon": {
    "light": "./media/PreviewOnRightPane_16x.svg",
    "dark": "./media/PreviewOnRightPane_16x_dark.svg"
  }
}],
"menus": {
		"editor/title": [
			{
				"command": "markdown.showPreviewToSide",
				"when": "editorLangId == markdown",
				"alt": "markdown.showPreview",
				"group": "navigation"
			}
  ]
}

command定義iconmenu關聯到command,然後menu展示對應的icon

擴充 API

環境隔離讓嚴格限制外掛程式可用 API 變得容易很多,外掛程式只能訪問 IDE 提供的擴充性 API,不能胡亂搞事情(比如修改 UI DOM 和樣式,官方支援的主題客製化項除外)。

API 設計原則

外掛程式 API 遵循一些原則:

  • 基於 Promise:非同步動作用 Promise 來描述

  • 取消 token:傳入CancellationToken作為額外參數來檢查取消狀態,以及接收取消通知

  • 可釋放式資源管理:持有的資源都需要手動釋放,例如事件監聽、命令、UI 互動等

  • 事件 API:呼叫訂閱方法(on[Will|Did]VerbNoun)傳入 listener(接收event參數)返回 Disposable

  • 嚴格空檢查:通過 TypeScript 嚴格區分undefinednull

P.S.關於「可釋放式」(Disposable)的更多資訊,請查看 Dispose pattern

API 概覽

API 按命名空間組織,全域命名空間如下:

commands 執行/註冊命令,IDE 自身的和其它外掛程式註冊的命令都可以,如 executeCommand
debug 除錯相關 API,比如 startDebugging
env IDE 相關的環境資訊,比如 machineId, sessionId
extensions 跨外掛程式 API 呼叫,extensionDependency 宣告外掛程式依賴
languages 程式語言相關 API,如 createDiagnosticCollection, registerDocumentFormattingEditProvider
scm 原始碼版本控制 API,如 createSourceControl
window 編輯器窗體相關 API,如 onDidChangeTextEditorSelection, createTerminal, showTextDocument
workspace 工作空間級 API(開啟了資料夾才有工作空間),如 findFiles, openTextDocument, saveAll

比如可以通過workspace.findFiles + languages.registerDefinitionProvider實作 Haste 的全域模組引用跳轉支援

另外,一些 API 以命令形式提供(即上面提到的「IDE 自身的」命令),例如vscode.previewHtmlvscode.openFoldereditorScroll等等。

基於協定的擴充

外掛程式行程與 IDE 之間通過特定協定來通訊,實作上是以 JSON 形式的 stdin/stdout 來通訊。

這種模式更強大的一點是:外掛程式可以用任意語言來實作,只要遵守這套約定的通訊協定即可。

四.語言相關擴充

通過設定檔案來支援語法高亮、程式碼片段和智慧括號匹配,更複雜的通過擴充 API 或 language server 來做。

設定型擴充

  • 語法高亮:基礎支援區分字串、註解、關鍵字等語法角色,高級支援變數、函數引用等語義區分

  • 程式碼片段:snippets 快捷輸入,基礎支援簡單佔位符,高級支援嵌套佔位符

  • 智慧括號匹配:高級支援自動補充成對出現的東西,比如括號、引號、跨行註解等

注意,語言擴充 VS Code 支援標準 Text Mate GrammartmLanguage格式),比如 Monaco Editor 的非主流 Monarch-style 友好很多,具體見 Colorization Clarification

程式設計型擴充

簡單設定搞不定的,都通過擴充 API(寫外掛程式)來實作,有 2 種方式:

  • 實作 language server protocol 與 IDE 通訊,完全獨立

  • 註冊 Provider 提供自定義能力,類似於 hook 的方式

使用上,第一種麻煩但更強大靈活,第二種方便直接但沒那麼靈活。支援的擴充能力如下:

  • hover 提示:基礎支援類型、文件等資訊,高級支援方法簽名語法高亮

  • 補全提示:高級支援在補全提示項旁邊展示額外資訊

  • 檢查報錯:基礎支援儲存時對開啟的檔案內容檢查報錯,高級支援對開啟的檔案目錄裡的任意資源檢查報錯

  • 方法簽名:基礎支援在方法簽名中包含參數說明文件

  • 跳轉到定義:基礎支援存在多處定義時都展示出來

  • 引用尋找:基礎支援返回所有引用處的具體位置

  • 選中尋找高亮:基礎支援返回當前文件的所有相同引用

  • 方法/變數宣告目錄:基礎支援返回文件中宣告的所有識別符,及其定義位置

  • 快速修復:對 Warning 和 Error 給出建議做法,快捷修復。基礎支援糾錯動作,高級支援修改原始碼,比如重複程式碼提出函數

  • 上下文操作選項:允許根據使用者處程式碼上下文,提供額外的資訊與可操作選項。基礎支援展示,高級可以新增自定義命令

  • 重新命名:基礎不支援按引用重新命名,高級支援工作空間下跨檔案重新命名

  • 程式碼格式化:基礎不支援程式碼格式化,高級支援全文/選中/輸入中格式化

五.開發步驟

環境要求

  • VS Code

  • Yeoman 與 Yo Code - Extension Generator:npm install -g yo generator-code一步搞定

步驟

通過鷹架生成專案模版:

yo code

命令互動選擇外掛程式類型:

New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Extension Pack

建議 TypeScript,其它都是字面意思,其中 Extension Pack(外掛程式包)比較有意思,即外掛程式組裝成的外掛程式,類似於 React Native 的 Nuclide

輸入外掛程式名稱等 meta 資訊,就得到一個外掛程式專案,然後用 VS Code單獨開啟該專案(工作空間不能有其它專案目錄),F5 啟動 debug 進入外掛程式除錯。

外掛程式入口檔案是my-extension/src/extension.ts,專案結構規範可以參照 VS Code 內建外掛程式

// ref: https://github.com/Microsoft/vscode/tree/master/extensions/markdown
markdown/
  media/
    *.svg
    *.css
  snippets/
    markdown.json
  syntaxes/
    *.tmLanguage
  src/
    features/
      *Provider.ts
    typings/
      *.d.ts
    commandManager.ts
    commands.ts
    logger.ts
    markdownEngine.ts
    security.ts
    telemetryReporter.ts

六.打包發布

提供了 CLI 工具,vsce

npm install -g vsce

打包

進入外掛程式目錄,打包成.vsix檔案:

cd my-extension
vsce package

會得到一個my-extesion.vsix本機包(包括node_modules依賴),然後不想公開的話,自己想辦法傳播安裝,因為不像 npm registry,可以 手動部署一份,在內網環境放私有外掛程式,Visual Studio Marketplace(VS Code 外掛程式市場)沒有這麼開放心態:

If you want to share your extension with others privately, you can send them your packaged extension .vsix file.

(見 Sharing Privately with Others

沒有辦法部署一套 Visual Studio Marketplace,所以只能想辦法手動解決外掛程式更新問題,比如自動下載/提示安裝。

發布

要發布到外掛程式市場的話,需要做幾件事情:

  1. 註冊 Visual Studio Team Services 帳號

  2. 進入 Security 頁面創建個 Personal Access Token

  3. vsce create-publisher (publisher name)命令新增 publisher

  4. vsce login (publisher name)命令登入

  5. vsce publish -p <token>命令發布

具體見 Publishing Extensions

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論