上一篇博文用修改 ~/Library/KeyBindings/DefaultKeyBinding.dict 的方法修改按键绑定,但很多地方依然无法响应新的键位设置,体验比较割裂,于是换到 Karabiner

实现方式的区别

DefaultKeyBinding

DefaultKeyBinding.dict 中,用 Ctrl+h 来向左移动,macOS 是通过这样实现的:

"^h" = "moveBackward:";

如果想继续实现 Shift+left(向左移动时选中文本)这个功能,还得需要调用另一个方法:

"^H" = "moveBackwardAndModifySelection:";

苹果原生的方式是直接作用的,比如删除到行首(Ctrl + u)就等于一步操作:直接删除到行首。

Karabiner

Karabiner 要实现删除到行首,它是先通过 Command + Shift + left 来选中光标之前所有的文本,再按下删除键,这样的方式来实现的。

影响可能是有的,比如终端上无法响应,如果自己定义了一个 Ctrl + u,反而还覆盖了其本身正常的功能,不过可以屏蔽掉不需要的 App。

另外无须单独实现带选区的那种,因为 Ctrl+h 已经被映射为左箭头了,自己再加上 Shift 就可以了。

Karabiner 还可以实现更多的功能,比如作者直接写了个全局 Vim 映射,还可以启动 App,设置只限定特定设备、特定 App 等等,功能着实强大,还开源免费。

简单使用

  • Simple Modifications,可以简单映射,比如 CapsLock → Control
  • Complex Modifications,可以引用别人已经写好了的规则。
    • 或者打开 ~/.config/karabiner/assets/complex_modifications 增加自己的规则。
  • Devices,选择要应用到的设备,比如内置键盘。
  • Profiles,定义多套方案,按需选择。

官方文档:https://karabiner-elements.pqrs.org/docs/

社区分享:https://ke-complex-modifications.pqrs.org/

建议查看官方文档熟悉一点小例子,然后下载别人实现类似的功能的 JSON 代码来参考。

文档最下面有个 External JSON generators,比如用这个 Karabiner Complex Modification 可以自动生成 JSON,不过简单的映射还是直接写 JSON 方便。

我的键位设置

原始的 Emacs-like 键位:

Ctrl + ...
a 移动到行首
e 移动到行尾
b 左
f 右
p 上
n 下
h 往左删除一个
d 往右删除一个
w 往左删除单词
u 往左删除到行首
k 往右删除到行尾

自定义的的键位:

Ctrl + ...
a 移动到行首
e 移动到行尾
h 左
l 右
k 上
j 下
o 插入一行
s 往左删除一个
d 往右删除一个
u 往左删除到行首
i 往右删除到行尾
w 往左删除单词

pnbf 仍然可以使用,没有冲突。

a e 不进行任何改动,我还没有发现有不支持的地方。

w u 的功能虽然和上面一样,但有些地方比如 Typora 就不支持,自己实现一遍确保全局有效。

代码

  • (1) CapsLock 短按为 Esc,组合为 Control
  • (2) Ctrl + hjkl 移动光标,排除 JetBrains
    • 上下左右修改为 hjkl,与原来的 pnbf 不冲突,仍然可以使用;
    • 排除 JetBrains,IDEA 自己单独实现普通模式和插入模式不同的行为。
  • (3) Ctrl + aeosduiw,排除 JetBrains 和终端
    • ae 不改动,wu 重新实现了一遍;
    • o 和 Vim 的功能类似,在本行的任何位置直接新建一行并切换过去;
    • 增加了 s、i 两个键位在原来的 d、u 旁边,左边的往左删,右边的往右删,很好记忆;
    • 排除 JetBrains 和终端。
  • (4) Option + [Shift] + hjkl
    • 可以用 Option [+ Shift] + Ctrl + h/l,但按键太多,改成直接按 h/l 也生效;
    • Option + j/k 用于 VSCode 默认快捷键,上下移动此行;
    • Option + Shift + j/k 用于 VSCode 默认快捷键,复制一行;

将代码保存到 ~/.config/karabiner/assets/complex_modifications

因为这个 issue#2774 没有解决,还不能全局限定或屏蔽某 App,代码像面条一样,frontmost_application_unless 相关配置每次修改时都需要同时改动多处地方。

{
  "title": "Dvel's hjkl",
  "rules": [
    {
      "description": "(1) CapsLock 短按为 Esc,组合为 Control",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "caps_lock",
            "modifiers": {
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_control",
              "lazy": true
            }
          ],
          "to_if_alone": [
            {
              "key_code": "escape"
            }
          ]
        }
      ]
    },
    {
      "description": "(2) Ctrl + hjkl 移动光标,排除 JetBrains",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "h",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "j",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "down_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "up_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "l",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "(3) Ctrl + aeosduiw,排除 JetBrains 和终端",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "o",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "left_command"
              ]
            },
            {
              "key_code": "return_or_enter"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "s",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "d",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_forward"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "u",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "left_command",
                "left_shift"
              ]
            },
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "i",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "left_command",
                "left_shift"
              ]
            },
            {
              "key_code": "delete_forward"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "w",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "option",
                "shift"
              ]
            },
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "(4) Option [+ Shift] + hjkl",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "h",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "j",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "down_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "up_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "l",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        }
      ]
    }
  ]
}