序言
入坑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
擴充套件的開發,官方便提供了一份不錯的教程。一個擴充套件有許多的“八股文”程式碼,可以用yo
和generator-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 Code
的API文件
後,我有了以下的線索:
- 通過
vscode.window.activeTextEditor
可以取得當前聚焦的編輯器——其值可能為空(undefined
); TextEditor
例項的屬性.selection.active
可以取得當前游標的位置;TextEditor
例項有一個方法revealRange
可以滾動文字來改變展示的範圍,它需要一個vscode.Range
類的例項,以及一個vscode.TextEditorRevealType
型別的列舉值;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
之外定義的變數:
previousPosition
負責記錄上一次呼叫recenterTop
時游標的位置,它的初始值為null
;revealType
儲存著上一次調整展示範圍時傳遞給TextEditor
例項的revealRange
方法的第二個引數的值,它的初始值也為null
。
我的目標是儘量模擬Emacs
中的recenter-top-bottom
所具備的、交替使用居中、置頂效果的特點,因此:
- 如果
revealType
為null
,意味著這是第一次呼叫recenterTop
,那麼效果便是居中。否則; - 如果這一次與上一次的游標位置不同,意味著在上一次呼叫
recenterTop
後呼叫過其它命令,效果依然是居中。否則; - 如果
revealType
已經是居中了,就改為置頂。否則; - 將
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.json
的contributes
物件中,新增名為keybindings
的屬性,並定義命令及按鍵序列即可。
{
// 此處省略其它不必要的屬性
"contributes": {
"keybindings":{ // 新增屬性
"command": "helloworld.helloWorld",
"key": "ctrl+l"
}
}
}
後記
如果看過我之前的文章《手指疼,寫點程式碼緩解一下》的讀者應當會記得,我已經從Emacs Keymap
“叛逃”到了Vim Keymap
了。所以,我並沒有真正用上上述的VS Code
擴充套件。相反,目前高頻使用的是Vim Keymap
內建的z-.
以及z-↵
了——前者用於垂直居中,後者用於置頂。
愛護手指,從使用Vim Keymap
做起。