不少编程大佬推崇 纯键盘工作流。熟练掌握各种快捷键和操作模式后,手指几乎不需要离开键盘,就能完成绝大部分编码工作,极大提升编码效率。
需要强调的是,纯键盘操作并不意味着完全摒弃鼠标。不同任务场景有不同的最优操作方式,关键是根据具体情况选择最合适的工具。我们的目标是建立一套平衡且高效的工作流程,让技术为生产力服务,而不是成为负担。无需强迫做一个看起来很厉害的纯键盘党。
本文主要从笔者实践角度出发,介绍几个关键的键盘效率提升方案:
- VSCode 编辑器:快捷键配置和 Vim 模式使用
- 浏览器导航:通过扩展实现键盘控制网页浏览
- 辅助软件:键位映射、输入法切换、窗口管理等
本文以 macOS 系统为准,相关配置和软件说明均在 keyboard-master 仓库。
Karabiner-Elements
Karabiner-Elements 是一款非常强大的键位修改软件。除了简单的键位映射,也支持 Complex Modifications 来处理复杂的键位映射。

配置高级映射比较复杂,所幸 Karabiner-Elements 内置了一些规则,如果内置规则不满足,也可以通过 Karabiner-Elements complex_modifications rules 获取别人分享的好用规则。
笔者使用的规则就两条:
-
Caps_Lock
键映射为 Escape 键和 Hyper 键 (即Shift
+Command
+Option
+Control
)- 单独按下
Caps_Lock
时,等同于按下Escape
键,手指不用跑那么远 - 按下
Caps_Lock
加上任意其他按键时,等同于按下Shift
+Command
+Option
+Control
加其他按键,在自定义 VSCode 快捷键时很有用,避免键位冲突,且按起来方便
- 单独按下
-
Caps_Lock
加上i/j/k/l
映射为方向键- 即 Vim 的移动方式,手指不用离开主键盘区域
- 在各种软件的下拉选项中使用尤其方便
VSCode
快捷键
除了常规快捷键设置外,VSCode 也提供了基于条件(when
属性)的快捷键设置,以及 runCommands
命令,可以通过它一次性执行多个操作。
when
的好处在于可以使用同一套快捷键,搭配不同条件实现不同场景的操作,减少设置多个快捷键位的麻烦。

比如,VSCode 设置可以是图形界面,也可以是 settings.json
配置文件。键盘快捷键设置也同理,既有图形页面,也有 keybindings.json
配置文件。通过 when
就可以实现快速切换两个界面:

json
[
// 打开键盘快捷键设置面板
{
"command": "workbench.action.openGlobalKeybindings",
"key": "cmd+k cmd+s"
},
// 在键盘快捷键设置面板中时,打开键盘快捷键 JSON 文件
{
"command": "workbench.action.openGlobalKeybindingsFile",
"key": "cmd+k cmd+s",
// 仅在键盘快捷键设置面板中生效
"when": "inKeybindings"
},
// 打开设置面板
{
"command": "workbench.action.openSettings",
"key": "cmd+,"
},
// 在设置面板中时,打开设置 JSON 文件
{
"command": "workbench.action.openSettingsJson",
"key": "cmd+,",
// 仅在设置编辑器中生效
"when": "inSettingsEditor"
}
]
when
的可配置条件非常多,建议仔细阅读 when clause contexts 官方文档,如果有些很细致的条件没有注明,文档最后也提到了如何通过 调试查看 context keys 信息:

简化文件操作
当我们处理资源管理器界面时,同样可以通过 when
简化创建文件、创建文件夹、重命名等操作。

json
[
// 新建文件(按键 A)
{
"command": "explorer.newFile",
"key": "a",
// 仅在文件资源管理器有焦点且没有输入焦点时生效
"when": "filesExplorerFocus && !inputFocus"
},
// 新建文件夹(按键 F)
{
"command": "explorer.newFolder",
"key": "f",
"when": "filesExplorerFocus && !inputFocus"
},
// 重命名文件(按键 R)
{
"command": "renameFile",
"key": "r",
"when": "filesExplorerFocus && !inputFocus"
},
// 复制文件(按键 Y)
{
"command": "filesExplorer.copy",
"key": "y",
"when": "filesExplorerFocus && !inputFocus"
},
// 剪切文件(按键 X)
{
"command": "filesExplorer.cut",
"key": "x",
"when": "filesExplorerFocus && !inputFocus"
},
// 粘贴文件(按键 P)
{
"command": "filesExplorer.paste",
"key": "p",
"when": "filesExplorerFocus && !inputFocus"
},
// 删除文件(按键 D)
{
"command": "deleteFile",
"key": "d",
"when": "filesExplorerFocus && !inputFocus"
},
// 复制相对文件路径(Ctrl+Shift+Alt+Cmd+C)
// 这里用到了 Karabiner-Elements 的 Hyper 组合键
{
"command": "copyRelativeFilePath",
"key": "ctrl+shift+alt+cmd+c"
},
// 折叠资源管理器文件夹(双击 Z)
{
"command": "workbench.files.action.collapseExplorerFolders",
"key": "z z",
"when": "filesExplorerFocus && !inputFocus"
}
]
简化全局搜索
全局搜索后,想快速定位到搜索结果,之前需要靠鼠标点击,现在可以通过确定后,直接使用 Tab
跳到搜索结果,如果此时想快速再回到搜索框,可以直接使用 Shift
+ Tab
(当然也可以再按全局搜索快捷键):

json
[
// 聚焦搜索结果列表(Tab)
{
"command": "search.action.focusSearchList",
"key": "tab",
// 仅在搜索视图有焦点时生效
"when": "searchViewletFocus"
},
// 返回搜索输入框(Shift+Tab)
// 没有直接 focus search input 的命令,所以采用了 runCommands,然后多次调用 previousInputBox 把焦点往前移动
// 最多只会存在 4 个输入框(搜索,替换,包含文件,排除文件),所以移动 4 次肯定能保证回到搜索框
{
"args": {
"commands": [
"search.focus.previousInputBox",
"search.focus.previousInputBox",
"search.focus.previousInputBox",
"search.focus.previousInputBox"
]
},
"command": "runCommands",
"key": "shift+tab",
// 仅在有搜索结果且搜索视图有焦点时生效
"when": "hasSearchResult && searchViewletFocus"
}
]
简化 Git 打开文件操作

如果安装了 GitLens 插件,通过 GitLens 打开的历史版本和通过 VSCode 自带代码管理器打开的文件 resourceScheme
会不同,并且打开文件背后所使用的命令也不同:
可以通过 Command
+ O
来统一通过历史版本直接打开对应的工作文件:

json
[
{
"key": "cmd+o",
"command": "runCommands",
"args": {
"commands": ["git.openFile", "gitlens.openWorkingFile"]
},
// 仅在 Git 相关的文件视图中且不在多文件比较编辑器中时生效
"when": "(resourceScheme =='gitlens' || resourceScheme == 'git' || scmProvider == 'git') && activeEditor != 'multiDiffEditor'"
},
// 关闭所有 Git 差异编辑器(Cmd+K Cmd+D)
{
"key": "cmd+k cmd+d",
"command": "git.closeAllDiffEditors"
},
// 禁用系统的打开文件操作,对我来说没啥用
{
"key": "cmd+o",
"command": "-workbench.action.files.openFileFolder",
"when": "isMacNative && openFolderWorkspaceSupport"
}
]
简化 Terminal 操作
VSCode 自带的打开 Terminal 的方式对手指不太好,将其改为 Ctrl
+ \
,并且支持在聚焦在 Terminal 中,通过 Command
+ W
关闭终端,和 Ctrl
+ M
最大化:

json
[
// 切换终端显示状态(Ctrl+\)
{
"command": "workbench.action.terminal.toggleTerminal",
"key": "ctrl+\\",
// 仅在终端处于活动状态时生效
"when": "terminal.active"
},
// 新建终端(Ctrl+Shift+\)
{
"command": "workbench.action.terminal.new",
"key": "ctrl+shift+\\",
// 仅在终端处于活动状态时生效
"when": "terminal.active"
},
// 关闭终端(Cmd+W)
{
"command": "workbench.action.terminal.kill",
"key": "cmd+w",
// 仅在终端可见且终端有焦点时生效
"when": "terminal.visible && terminalFocus"
},
// 分割终端(Ctrl+Shift+Alt+Cmd+\)
{
"command": "workbench.action.terminal.split",
"key": "ctrl+shift+alt+cmd+\\",
// 仅在终端有焦点时生效
"when": "terminalFocus"
},
// 切换面板最大化状态(Ctrl+M)
{
"command": "workbench.action.toggleMaximizedPanel",
"key": "ctrl+m",
// 仅在终端有焦点时生效
"when": "terminalFocus"
}
]
Tips:如何知道这些 command 的 ID 呢?在键盘快捷方式中,选择一个命令右键复制命令 ID 即可。

Vim 模式
接下来来到了本文的重头戏:Vim 模式。
学习 Vim
本篇文章不是 Vim 使用教程,对于非 Vim 用户,通过 Learn Vim 这个插件来快速学习最核心的一些操作:

Vim Cheatsheet 插件是 Vim 按键的说明:

如果安装 Vim 的同学,也可以直接通过 vimtutor 学习:

学习 Vim 比较重要的一点就是 "刻意练习",尤其是一开始接触时。笔者也是老听一些大佬吹 Vim 的强大,尝试学习了好几次,也能确实感觉到一些操作的便捷性,但最终也没有真正将其融入到实际工作开发中。比如现在的 IDE 已经那么强大,各种快捷键、重构等方式足够快,然后要用 Vim,要将 Vim 打造到 IDE 那么强大,太耗时间。即使现在也有了 NeoVim 这种更现代化的 Vim,再搭配 LazyVim 可以简化配置的大杀器,确实降低了很多配置成本。对于喜欢折腾的同学,从零打造一个完全由自己掌握的 IDE 的感觉是很爽的。
笔者也喜欢折腾,但只能折腾一点,也使用 Neovim 搭建过,搭了一个基础版就放弃了,无法割舍对 IDE 已有功能的喜爱。直到发现了 VSCode 中 Vim 的插件模式,既可以保留对 VSCode 自身能力的使用,又能借助 Vim 的一些强大特性,才真正让我开始对 Vim 进行使用。
VSCode 里面的 Vim 模式插件主要有两种:

该插件模仿了 Vim 编辑器的键绑定和行为,提供了大多数 Vim 的原生功能,比如模式切换(普通模式、插入模式、可视模式等)、快捷键和命令。
优点:
- 无需外部依赖:直接在 VSCode 中实现 Vim 功能,不需要安装额外的 Vim 或 NeoVim(实验特性可以部分支持 NeoVim)
- 易于安装和使用:从 VSCode 插件市场直接安装,配置较为简单
- 良好的集成:与 VSCode 的功能(比如代码补全、片段、调试等)集成得较好
缺点:
- 性能问题:尽管对大多数场景已经足够,但对于一些高级功能或大文件,可能会有性能瓶颈
- 功能限制:虽然包含了大部分 Vim 功能,但仍有一些高级特性和插件不可用或难以实现

插件集成了 NeoVim,在非 Insert 模式时,其实是在后台运行 NeoVim 实例,并将其编辑功能带到了 VSCode 中。
优点:
- 更接近真正的 Vim/NeoVim:由于基于 NeoVim,用户可以享受到几乎所有 NeoVim 的特性,包括对 Vim 脚本的支持
- 更好的性能:对于复杂的操作和大文件,NeoVim 插件通常表现出更好的性能
- 更广泛的功能支持:利用 NeoVim 的强大功能,几乎可以使用所有 Vim/NeoVim 插件和配置,提供了更广泛的定制选项
缺点:
- 需要外部依赖:需要用户先安装 NeoVim,并自行配置各种特性,这对一些用户可能是额外的负担
- 可能的集成问题:虽然功能强大,但和 VSCode 自身能力的集成可能不如纯 Vim 插件那么密切,尤其是在 UI 集成、代码补全和诊断工具方面
笔者使用 Vim 插件,在使用 Vim 之前强烈建议仔细阅读一下插件的使用文档。这里也推荐这个不错的学习资源:
- Just Vim It:系统性介绍 VSCode、Vim 等使用技巧
- 指尖飞舞:VSCode + Vim 高效开发:B 站系统性讲解 VSCode Vim 插件的使用
一些预设值
安装 Vim 插件后,大多数面板都可以直接使用 i/j/k/l
进行移动,为了能按下按键后持续移动,还需要一些设置:
bash
defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false # For VS Code
defaults write com.microsoft.VSCodeInsiders ApplePressAndHoldEnabled -bool false # For VS Code Insider
defaults write com.vscodium ApplePressAndHoldEnabled -bool false # For VS Codium
defaults write com.microsoft.VSCodeExploration ApplePressAndHoldEnabled -bool false # For VS Codium Exploration users
defaults delete -g ApplePressAndHoldEnabled # If necessary, reset global default
并且推荐将键盘的速率调快:

设置 Vim 的 Leader 键位,大多数 Vim 配置都是使用空格,开启相对行数:
json
{
"vim.leader": "<space>", // 设置 Leader 键为空格键
"editor.lineNumbers": "relative", // 开启相对行号,当需要操作 x 行时,可以很方便通过相对行操作
"vim.smartRelativeLine": true // 智能相对行号(插入模式显示绝对行号)
}
快速移动 Sneak & EasyMotion
除了 b
、e
、w
这些常规移动外,通过 Sneak & EasyMotion 可以实现快速移动到某个位置,比如移动到一个名为 tool
的变量,可以直接使用 f
+to
(名字中随便两个相邻的字符即可)就可以快速跳过去,如果在光标和当前目标中间还有 to
字符匹配,就需要按下 ;
再次跳一下,对于相隔几行的跳转很快:

如果是隔得比较远了,就需要用到 EasyMotion,它会给所有命中的位置给一个再次选择的高亮提示,保证能精准跳过去:

json
{
// Vim Sneak 插件配置(快速双字符搜索)
"vim.sneak": true, // 启用 Sneak 功能
"vim.sneakUseIgnorecaseAndSmartcase": true, // Sneak 使用忽略大小写和智能大小写
"vim.easymotion": true, // 启用 EasyMotion 快速移动功能
"vim.normalModeKeyBindingsNonRecursive": [
// 自定义移动键位映射(重新映射 f/F 为 s/S)
{
"after": ["s"], // 将原本的 f 键功能映射到 s 键
"before": ["f"]
},
{
"after": ["S"], // 将原本的 F 键功能映射到 S 键
"before": ["F"]
},
{
"after": ["c", "l"], // 将 s 键重新映射为 cl(change letter)
"before": ["s"]
},
{
"after": ["^", "C"], // 将 S 键重新映射为行首删除到行尾
"before": ["S"]
},
// EasyMotion 快速移动键位映射
{
"after": ["leader", "leader", "2", "s"], // Leader+s 映射到 EasyMotion 搜索
"before": ["leader", "s"]
},
{
"after": ["leader", "leader", "2", "f"], // Leader+f 映射到 EasyMotion 查找
"before": ["leader", "f"]
},
{
"after": ["leader", "leader", "2", "t"], // Leader+t 映射到 EasyMotion 到字符前
"before": ["leader", "t"]
}
],
"vim.visualModeKeyBindingsNonRecursive": [
// 重新映射查找键位(与普通模式保持一致)
{
"after": ["s"], // f 映射到 s
"before": ["f"]
},
{
"after": ["S"], // F 映射到 S
"before": ["F"]
},
// EasyMotion 快速移动(可视模式)
{
"after": ["leader", "leader", "2", "s"], // Leader+s 用于 EasyMotion 搜索
"before": ["leader", "s"]
},
{
"after": ["leader", "leader", "2", "f"], // Leader+f 用于 EasyMotion 查找
"before": ["leader", "f"]
},
{
"after": ["leader", "leader", "2", "t"], // Leader+t 用于 EasyMotion 到字符前
"before": ["leader", "t"]
}
],
"vim.operatorPendingModeKeyBindingsNonRecursive": [
// 重新映射查找键位
{
"after": ["z"], // 将 f 功能映射到 z(保持与普通模式一致)
"before": ["f"]
},
{
"after": ["Z"], // 将 F 功能映射到 Z(保持与普通模式一致)
"before": ["F"]
}
]
}
LSP 操作
找到了具体的地方,接下来就会进行一些代码的 LSP 查看工作,比如查看定义、引用等等。

json
{
"vim.normalModeKeyBindingsNonRecursive": [
// 代码导航和信息查看操作(g 系列命令)
{
"before": ["g", "h"], // gh 显示定义预览悬停
"commands": ["editor.action.showDefinitionPreviewHover"]
},
{
"before": ["g", "d"], // gd 跳转到定义
"commands": ["editor.action.revealDefinition"]
},
{
"before": ["g", "p", "d"], // gpd 预览定义(不跳转)
"commands": ["editor.action.peekDefinition"]
},
{
"before": ["g", "i"], // gi 跳转到实现
"commands": ["editor.action.goToImplementation"]
},
{
"before": ["g", "p", "i"], // gpi 预览实现(不跳转)
"commands": ["editor.action.peekImplementation"]
},
{
"before": ["g", "t"], // gt 跳转到类型定义
"commands": ["editor.action.goToTypeDefinition"]
},
{
"before": ["g", "p", "t"], // gpt 预览类型定义(不跳转)
"commands": ["editor.action.peekTypeDefinition"]
},
{
"before": ["g", "r"], // gr 查找所有引用
"commands": ["references-view.findReferences"]
},
{
"before": ["g", "p", "r"], // gpr 预览引用(不跳转)
"commands": ["editor.action.referenceSearch.trigger"]
},
{
"before": ["g", "q"], // gq 快速修复
"commands": ["editor.action.quickFix"]
},
{
"before": ["g", "s"], // gs 触发智能建议
"commands": ["editor.action.triggerSuggest"]
},
{
"before": ["g", "b"], // gb 选择下一个相同项
"commands": ["editor.action.addSelectionToNextFindMatch"]
},
{
"before": ["g", "B"], // gB 选择所有相同项
"commands": ["editor.action.selectHighlights"]
}
]
}
搭配 Hover 的按键设置,即可实现 Hover 里面内容同样使用 i/j/k/l
进行移动:
json
[
{
"key": "j",
"command": "editor.action.scrollDownHover",
"when": "editorHoverFocused"
},
{
"key": "k",
"command": "editor.action.scrollUpHover",
"when": "editorHoverFocused"
},
{
"key": "h",
"command": "editor.action.scrollLeftHover",
"when": "editorHoverFocused"
},
{
"key": "l",
"command": "editor.action.scrollRightHover",
"when": "editorHoverFocused"
}
]
也可以不配置,使用前文提到的
Caps_Lock
加上i/j/k/l
映射为方向键也能很方便移动
代码编辑
通过 Motion 以及 vim-textobj-arguments、vim-indent-object 等能力可以方便实现对区块、参数的编辑:

对代码进行包裹也是很常见的操作,比如使用 {}
包裹,或者是删除包裹块,在进行 React 开发时,可能还会包裹一个组件,或是删除父组件,借助 vim-surround 可以快速处理:

vim-surround 只能添加括号、引号、标签等,如果想要包裹其他的,比如 if
节点,可以借助 Surround 插件:


json
{
"vim.visualModeKeyBindingsNonRecursive": [
// 环绕操作
{
"before": ["r"], // r 用选择的字符环绕选中内容
"commands": ["surround.with"]
}
]
}
不过 Vim 和该插件插入后的编辑模式匹配不是太好。
Normal 模式下自动切换输入法
在 Insert 模式下,如果输入了中文,又没有将输入法切回英文模式,再切回 Normal 模式进行操作时,还是会展示中文输入框:

通过 Vim 的 input method 配置,搭配 im-select 可以解决输入法问题,在切换回 Normal 模式时,会自动将输入法切换为英文模式:

文中更多的只是展示一下特性,完整的 Vim 配置详见 keyboard-master 仓库。
Chrome Vim 模式
除了 IDE,浏览器几乎就是第二高频使用的软件,浏览器的常见操作大概就是:输入搜索、内容查看、内容滚动、链接点击跳转、筛选等等。这些也都可以通过 SurfingKeys 插件使用键盘来快速操作:


按下 ?
即可打开 Cheatsheet,功能非常多,记住常用的就 OK:

而且按键也是有规律的,比如 o
开头的是打开 xxx,s
开头的是搜索等等。输入一个字符,右下角也会跳出提示:

其他软件推荐
Raycast
Raycast 是 Alfred 的替代品,通过 Raycast,可以非常方便使用键盘操作各种命令,并且提供了非常多插件扩展使用,免费版也足够强大。
窗口操作
软件位置移动、窗口放大缩小,同样可以使用键盘来处理,独立的软件推荐 Rectangle。

如果你已经安装了 Raycast,里面自带了窗口管理的能力,可以使用 Rectangle 快捷键预设,如果觉得不顺手,可以自定义,借助本文提到的 Karabiner-Elements 能力:

软件切换
Mac 最麻烦的就是自带的软件应用来回切换,如果某个软件最小化了,切过去后,依旧是最小化的模式,不会自动打开,需要手动点开。
可以借助 Raycast 中的 Navigation 能力优化操作,切过去时会自动打开最小化的应用:


如果觉得键盘输入不直观,还有一个软件 AltTab 也能解决这个问题,展现形式和系统自带的 UI 效果类似:

输入法切换 Input Source Pro
前面提到了针对 Vim 输入法切换的 im-select,而 Input Source Pro 则是针对所有软件的,可以针对不同的软件设置默认的输入法,比如在切换到终端软件 Warp 或者 iTerm2 时,自动切换为英文输入:

总结
无论是哪种软件的辅助,都离不开刻意练习,都需要花费不小时间成本,喜欢折腾的同学不妨试试看,不喜欢也无妨。键盘编程也不是什么必需的手段,也不是银弹,只是多一种选择。