序言
入坑VS Code前,我已经是一名久经考验的Emacs老用户了,因此开始正式使用VS Code后,我第一时间启用了它的Emacs Keymap。但不久我便发现,这套键映射缺少一个重要的快捷键——ctrl-l。
在Emacs中,ctrl-l对应的命令是recenter-top-bottom,它用于将光标所在的行轮替地滚动到可视区域(即Emacs中的window)的中间、顶部,以及底部(如下图所示)
![5f4c0644dd2f85deefbbb2b24502f897.gif](https://img-blog.csdnimg.cn/img_convert/5f4c0644dd2f85deefbbb2b24502f897.gif)
这是我高频使用的一个功能,尤其是跳转到函数的定义的首行后,我习惯于连按两次,将其滚动到window的顶部以便在一屏中看到尽量多的内容。
为了避免重复发明轮子,我先搜索了一番,找到了一个宣称实现了该功能的扩展Recenter Top Bottom。可惜的是,安装后并不生效。
难道只能委屈自己用鼠标小心翼翼地将光标所在行滚到顶部了吗?当然不是。既然没有开箱即用的,那便自己写一个VS Code的扩展实现这个功能吧。
年轻人的第一个 VS Code 扩展
创建 VS Code 扩展的项目
要想入门VS Code扩展的开发,官方便提供了一份不错的教程。一个扩展有许多的“八股文”代码,可以用yo和generator-code来快速生成
npm install -g yo generator-codeyo 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的命令面板来调用
![9413379dc90d1a33ade84b65b71aef57.gif](https://img-blog.csdnimg.cn/img_convert/9413379dc90d1a33ade84b65b71aef57.gif)
实现将光标所在行置顶的功能
接下来我将实现连续调用两次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做起。