年輕人的第一個VSCode擴充套件

Liutos發表於2021-01-02

序言

入坑VS Code前,我已經是一名久經考驗的Emacs老使用者了,因此開始正式使用VS Code後,我第一時間啟用了它的Emacs Keymap。但不久我便發現,這套鍵對映缺少一個重要的快捷鍵——ctrl-l

Emacs中,ctrl-l對應的命令是recenter-top-bottom,它用於將游標所在的行輪替地滾動到可視區域(即Emacs中的window)的中間、頂部,以及底部(如下圖所示)

這是我高頻使用的一個功能,尤其是跳轉到函式的定義的首行後,我習慣於連按兩次,將其滾動到window的頂部以便在一屏中看到儘量多的內容。

為了避免重複發明輪子,我先搜尋了一番,找到了一個宣稱實現了該功能的擴充套件Recenter Top Bottom。可惜的是,安裝後並不生效。

難道只能委屈自己用滑鼠小心翼翼地將游標所在行滾到頂部了嗎?當然不是。既然沒有開箱即用的,那便自己寫一個VS Code的擴充套件實現這個功能吧。

年輕人的第一個VS Code擴充套件

建立VS Code擴充套件的專案

要想入門VS Code擴充套件的開發,官方便提供了一份不錯的教程。一個擴充套件有許多的“八股文”程式碼,可以用yogenerator-code來快速生成

npm install -g yo generator-code
yo code

到這裡,便得到了一個名為helloworld的目錄了。用VS Code開啟它,接下來要在其中大展身手。

實現將游標所在行垂直居中的功能

VS Code擴充套件的核心邏輯定義在檔案src/extension.ts中。在yo生成的示例程式碼中,用registerCommand註冊了一個名為helloworld.helloWorld的命令,其邏輯是簡單地在右下角彈出一句Hello VS Code from HelloWorld!。這個回撥函式,便是業務邏輯的落腳點。

要想實現將游標所在行滾動到中間的功能,首先要知道VS Code為開發者提供了哪些支援。在摸索了一通從VS CodeAPI文件後,我有了以下的線索:

  1. 通過vscode.window.activeTextEditor可以取得當前聚焦的編輯器——其值可能為空(undefined);
  2. TextEditor例項的屬性.selection.active可以取得當前游標的位置;
  3. TextEditor例項有一個方法revealRange可以滾動文字來改變展示的範圍,它需要一個vscode.Range類的例項,以及一個vscode.TextEditorRevealType型別的列舉值;
  4. vscode.TextEditorRevealType.InCenter的效果是將所給定的範圍展示在中間,vscode.TextEditorRevealType.AtTop則是置頂。

有了這些知識儲備,實現這樣的一個回撥函式便是信手拈來的事情了

function recenterTop() {
  const editor = vscode.window.activeTextEditor;
  if (!editor) {
    return;
  }
  const cursorPosition = editor.selection.active;
  editor.revealRange(new vscode.Range(cursorPosition, cursorPosition), vscode.TextEditorRevealType.InCenter);
}

由於暫時沒有配置該命令的快捷鍵,只能用VS Code的命令皮膚來呼叫

實現將游標所在行置頂的功能

接下來我將實現連續呼叫兩次helloworld.helloWorld命令,把游標所在行滾動到頂部的效果。在Emacs中,可以很輕鬆地知道一個命令是否被連續執行——Emacs有一個名為last-command的變數儲存著上一個命令的名稱,只需要檢查其是否等於recenter-top-bottom即可。但VS Code沒有暴露這麼強大的功能,只能另闢蹊徑。

我的策略是,如果呼叫helloworld.helloWorld時游標的位置,與上一次呼叫該命令時的位置相同,就認為是連續呼叫。為此,需要兩個在函式recenterTop之外定義的變數:

  1. previousPosition負責記錄上一次呼叫recenterTop時游標的位置,它的初始值為null
  2. revealType儲存著上一次調整展示範圍時傳遞給TextEditor例項的revealRange方法的第二個引數的值,它的初始值也為null

我的目標是儘量模擬Emacs中的recenter-top-bottom所具備的、交替使用居中、置頂效果的特點,因此:

  1. 如果revealTypenull,意味著這是第一次呼叫recenterTop,那麼效果便是居中。否則;
  2. 如果這一次與上一次的游標位置不同,意味著在上一次呼叫recenterTop後呼叫過其它命令,效果依然是居中。否則;
  3. 如果revealType已經是居中了,就改為置頂。否則;
  4. revealType改為居中。
Talk is cheap. Show me the code.
let previousPosition: null|vscode.Position = null;
let revealType: null|vscode.TextEditorRevealType = null;

function recenterTop() {
  const editor = vscode.window.activeTextEditor;
  if (!editor) {
    return;
  }
  const cursorPosition = editor.selection.active;
  if (!revealType) {
    revealType = vscode.TextEditorRevealType.InCenter;
  } else if (previousPosition && !cursorPosition.isEqual(previousPosition)) {
    revealType = vscode.TextEditorRevealType.InCenter;
  } else if (revealType === vscode.TextEditorRevealType.InCenter) {
    revealType = vscode.TextEditorRevealType.AtTop;
  } else {
    revealType = vscode.TextEditorRevealType.InCenter;
  }
  previousPosition = cursorPosition;
  editor.revealRange(new vscode.Range(cursorPosition, cursorPosition), revealType);
}

定義快捷鍵

通過命令皮膚來使用不是我的最終目標,通過快捷鍵才是。根據VS Code文件可以知道,只要在package.jsoncontributes物件中,新增名為keybindings的屬性,並定義命令及按鍵序列即可。

{
  // 此處省略其它不必要的屬性
  "contributes": {
    "keybindings":{ // 新增屬性
      "command": "helloworld.helloWorld",
      "key": "ctrl+l"
    }
  }
}

後記

如果看過我之前的文章《手指疼,寫點程式碼緩解一下》的讀者應當會記得,我已經從Emacs Keymap“叛逃”到了Vim Keymap了。所以,我並沒有真正用上上述的VS Code擴充套件。相反,目前高頻使用的是Vim Keymap內建的z-.以及z-↵了——前者用於垂直居中,後者用於置頂。

愛護手指,從使用Vim Keymap做起。

閱讀原文

相關文章