键盘效率提升指南(VSCode+Vim+SurfingKeys)

不少编程大佬推崇 纯键盘工作流。熟练掌握各种快捷键和操作模式后,手指几乎不需要离开键盘,就能完成绝大部分编码工作,极大提升编码效率。

需要强调的是,纯键盘操作并不意味着完全摒弃鼠标。不同任务场景有不同的最优操作方式,关键是根据具体情况选择最合适的工具。我们的目标是建立一套平衡且高效的工作流程,让技术为生产力服务,而不是成为负担。无需强迫做一个看起来很厉害的纯键盘党。

本文主要从笔者实践角度出发,介绍几个关键的键盘效率提升方案:

  • VSCode 编辑器:快捷键配置和 Vim 模式使用
  • 浏览器导航:通过扩展实现键盘控制网页浏览
  • 辅助软件:键位映射、输入法切换、窗口管理等

本文以 macOS 系统为准,相关配置和软件说明均在 keyboard-master 仓库。

Karabiner-Elements

Karabiner-Elements 是一款非常强大的键位修改软件。除了简单的键位映射,也支持 Complex Modifications 来处理复杂的键位映射。

配置高级映射比较复杂,所幸 Karabiner-Elements 内置了一些规则,如果内置规则不满足,也可以通过 Karabiner-Elements complex_modifications rules 获取别人分享的好用规则。

笔者使用的规则就两条:

  1. Caps_Lock 键映射为 Escape 键和 Hyper 键 (即 Shift+Command+Option+Control

    • 单独按下 Caps_Lock 时,等同于按下 Escape 键,手指不用跑那么远
    • 按下 Caps_Lock 加上任意其他按键时,等同于按下 Shift+Command+Option+Control 加其他按键,在自定义 VSCode 快捷键时很有用,避免键位冲突,且按起来方便
  2. 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 编辑器的键绑定和行为,提供了大多数 Vim 的原生功能,比如模式切换(普通模式、插入模式、可视模式等)、快捷键和命令。

优点

  • 无需外部依赖:直接在 VSCode 中实现 Vim 功能,不需要安装额外的 Vim 或 NeoVim(实验特性可以部分支持 NeoVim)
  • 易于安装和使用:从 VSCode 插件市场直接安装,配置较为简单
  • 良好的集成:与 VSCode 的功能(比如代码补全、片段、调试等)集成得较好

缺点

  • 性能问题:尽管对大多数场景已经足够,但对于一些高级功能或大文件,可能会有性能瓶颈
  • 功能限制:虽然包含了大部分 Vim 功能,但仍有一些高级特性和插件不可用或难以实现

VSCode Neovim

插件集成了 NeoVim,在非 Insert 模式时,其实是在后台运行 NeoVim 实例,并将其编辑功能带到了 VSCode 中。

优点

  • 更接近真正的 Vim/NeoVim:由于基于 NeoVim,用户可以享受到几乎所有 NeoVim 的特性,包括对 Vim 脚本的支持
  • 更好的性能:对于复杂的操作和大文件,NeoVim 插件通常表现出更好的性能
  • 更广泛的功能支持:利用 NeoVim 的强大功能,几乎可以使用所有 Vim/NeoVim 插件和配置,提供了更广泛的定制选项

缺点

  • 需要外部依赖:需要用户先安装 NeoVim,并自行配置各种特性,这对一些用户可能是额外的负担
  • 可能的集成问题:虽然功能强大,但和 VSCode 自身能力的集成可能不如纯 Vim 插件那么密切,尤其是在 UI 集成、代码补全和诊断工具方面

笔者使用 Vim 插件,在使用 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

除了 bew 这些常规移动外,通过 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-argumentsvim-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 时,自动切换为英文输入:

总结

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

相关推荐
咖啡の猫1 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲3 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5814 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路4 小时前
GeoTools 读取影像元数据
前端
ssshooter4 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry5 小时前
Jetpack Compose 中的状态
前端
dae bal6 小时前
关于RSA和AES加密
前端·vue.js
柳杉6 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog6 小时前
低端设备加载webp ANR
前端·算法
LKAI.6 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi