Pi Store
更多

Cocoa 文本组合键:macOS 中隐藏的 Emacs 情怀

2023 年 07 月 21 日

在 macOS 中,隐藏着一整套来源于经典编辑器 Emacs 的文本编辑组合键,熟练掌握可以省去很多移动手腕功夫,减少疲劳并提高编辑效率。


快,五秒钟内回答:macOS 和 Emacs 有什么共同点?

你可能说,太简单了,两个名字里都有一个 MAC?

没毛病……但考虑到本文的主题不是化妆品,我们还是看看苹果自己的回答

[macOS 的] 文本系统具有一套通用的组合键机制,完全可由用户重新设定,[…] 标准组合键包含大量与 Emacs 兼容的 Control 组合键 […]

如果你是 macOS 的老用户,可能已经知道这是在说什么:在 macOS 上编辑文本时,除了人尽皆知的 Command-C/V/X/A 等等,系统还支持一整套涵盖了光标移动、文本选择、编辑插入等功能的组合键(keybindings),其中很多来源于经典编辑器 Emacs,并且从八九十年代的 macOS 前身 NeXTSTEP 一直延续至今。

45 年前的 Emacs 使用手册,其中很多在 macOS 中可以直接使用

初试身手

那么,这些组合键都能做什么,相比于如今更常用的版本有什么优势呢?下面这张动图可以帮你建立一个初步印象:

不难看出,这些组合键的特点是都以 Control 键为基础,而且虽然涉及很多移动光标操作,但都没有用到任何方向键。因此,掌握熟练之后,可以省去很多移动手腕去摸鼠标和方向键的功夫,减少疲劳并提高编辑效率。


插曲:交换 Control 和 Caps Lock 键

在继续跟随后文上手之前,我非常建议通过系统设置把 Control 和 Caps Lock 键交换位置。需要承认,这肯定不是一个特别大众的设置方法,但据我观察不乏拥趸,而且用过的很少不说好。

为什么建议这么改?如你应该已经发现的那样,Cocoa 文本组合键重度依赖 Control。因此,如果你习惯使用这套组合键,将 Control 键放在 Caps Lock 键的位置,比每次都伸出「兰花指」去键盘左下角的原键位要舒服得多。事实上,这也是早期 IBM 电脑用过的经典布局,后来又被 HHKB 等品牌沿用——甚至苹果的官方支持都将它作为改法示例。

要修改这个设置,首先打开「系统设置」的「键盘」部分,点按右侧的「键盘快捷键」,然后选择左侧列表中的「修饰键」。在弹出的面板中,先确认上方选择的是当前使用的键盘,然后将 Control 键和 Caps Lock 键的操作分别改为对方即可。


乍看起来,使用这些组合键要记一堆字母,有一定学习门槛。但只要掌握了规律,记起来其实是很快的:

首先,大多操作都是「Control-字母键」的组合,其中的字母基本就是操作对应的单词首字母:向后(backward)、向前(forward)、上一行(previous)、下一行(next)、行尾(end)、删除(delete)、交换前后字符(transpose)、插入换行并使光标留在原地(open-line)等。一个例外是移动到段首的 Control-A,据当事人回忆,这只是因为……A 是字母表之首而已。很多历史就是这么任性。

其次,更复杂的组合键主要是在移动操作的基础上演变而来:加上 Shift,就变成了移动光标并选择;加上 Option,就变成了以单词为单位移动(甚至支持基础的中文分词)。如果同时加上 Option 和 Shift?当然就是以单词为单位移动并选择。

一组比较有年代感、可能需要额外解释的操作是 Control-K 和 Control-Y。K 的意思是 kill,功能是从当前光标处删除到段尾,并把删除的内容暂存到一个称为 kill ring 的容器中;Y 的意思是 yank,把之前 kill 的东西「抽」出来,放回当前光标位置。(如果连续按多次 Control-K 后再按 Control-Y,之前吃掉的多行会被合并在一起放出来。)

抛开这对莫名其妙的术语——上世纪七八十年代黑客有些独特的脑回路是可以理解的——可以姑且将其理解成独立于系统剪贴板、只适用于当前窗口的特殊剪切和粘贴。对于长文和代码编辑场景,这种从一行中间往后剪切、同时又不挤占剪贴板的能力是很实用的。

追本溯源

当然,即使你之前完全没有听说过这些组合键,这也不是你的问题。

一方面,它们确实年代久远、讨论不多;另一方面,macOS 也几乎没有给它们提供任何「曝光」的机会:不仅没有在菜单栏中列举,连「系统设置」中的快捷键设置都难觅踪影,唯一比较正式的提及也藏在官方「Mac 键盘快捷键」列表的深处,可能主要只有 Emacs 的真爱粉能通过肌肉记忆偶然发现。

但就算这套组合键再隐晦,既然存在于系统中,它总得有个来头,也总得在什么地方留下些痕迹吧?

答案是肯定的。操作系统的主要任务之一就是为应用程序做好各种「幕后工作」。而这些幕后工作中,很重要的一项就是文本处理,包括字符显示、格式排版、文本编辑等等。

在 macOS 中,负责文本处理的组件称为「Cocoa 文本系统」(Cocoa text system),是 Cocoa(macOS 原生应用 API)的一部分。本文介绍的这套 Emacs 风格组合键,就是由 Cocoa 文本系统负责响应的。

至于系统默认的组合键,上面介绍的只是冰山一角,完整的「目录」位于 /System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict。这是一个二进制编码的属性列表(plist)文件,本身不方便阅读。如果你安装了 Xcode,可以直接双击打开查看内容。或者,也可以运行:

plutil -convert json -o - /System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict | jq --ascii-output --sort-keys . > StandardKeyBinding.dict

其中,plutil 将原文件转化为 JSON 格式,打印到标准输出;jq(需要安装)转换其中的一些特殊字符为 Unicode 码位,并按键名排序;最后写入当前目录。(想偷懒可以直接看我保存的结果。)

StandardKeyBinding.dict 片段

这里乱七八糟的符号有点多,但仍然有章法。大致的格式是:每个组合键用一个键—值对表示,键名是指定物理按键的字符串,值是一个数组,表示按下该组合键时要调用的一个或一组操作(称为「选择器」[selector])。

其中,表示物理按键的字符主要是:

符号 含义
^ Control
~ Option
$ Shift
@ Command
小写字母 对应字母键本身
大写字母 Shift 加对应字母键
\u 开头的 Unicode 码位序列 对应的 ASCII 控制字符按键苹果的私有保留码位按键

更完整的说明可参见 Jacob Rus 最早写于 2006 年的指南;事实上,Rus 此文基本是网络上所有介绍 Cocoa 文本组合键文章的共同参考资料。

至于按键要对应的操作,则都是驼峰拼写、冒号结尾的方法,命名均来自 AppKit 中负责响应输入的 NSResponder(有个文档,虽然内容少到等于没有);但其实一般用户并不需要关注这些开发上的细节,看单词就足以猜出大部分意思。

照此「翻译」,我们就得知了所有 macOS 中「隐藏」的 Cocoa 文本组合键(排除了一些过于常见、没有实际功能和现代 Mac 上找不到的组合键):

一些补充说明:

  • 「前」(forward)、「后」(backward)是相对于书写方向而言。例如对于中英文,向前是指向右,向后是指向左。
  • 如未附加具体单位,「前」「后」「左」「右」均指一个字符,「上」「下」均指一行。
  • 「段」和「行」是两个不同概念。如果开启了按窗口边界/字符边界换行,一段可能在视觉上跨越多行(soft wrap),此时以「行」为单位的操作(例如 Control-N/P 和方向键)会在这些视觉分行之间移动,而以「段」为单位的操作(例如 Control-A/E/K)则会跳过这些视觉分行,只考虑以回车开启的「硬换行」(hard wrap)。
  • 「删除」操作均以当前光标位置为起点,并包含所选中的部分。
  • 「忽略输入框限制」(ignore field editor)是指对于表单等输入框,将 Enter 键、Tab 等按键视作字面含义,即只插入换行符、制表符等字符,不触发确认、切换到下一栏等特殊操作。

更完整 Cocoa 文本操作整理仍然可参考 Rus 的列表。从中也可以看出,这些操作几乎涵盖了文本编辑的方方面面:除了插入、删除、剪切、大小写转换、翻页、选中等通用文本操作,还包括针对富文本格的字体、样式和版式操作,甚至还有保存和关闭文档、调节窗口位置和大小等针对文本编辑环境的操作;系统内置的组合键只用到其中很小一部分。


插曲:自动重复多次组合键

Vim 用户一定很喜欢它用「数字 + 操作键」重复多次操作的功能,例如 10j 就可以下移光标 10 次,5dw 就可以向前删除 5 个单词等等。

对此,Cocoa 文本系统表示……我也行。但需要做一个设置。打开终端,执行:

defaults write -g NSRepeatCountBinding -string "^r"

然后重新登录让修改生效,就可以启用重复组合键功能,并将 Control-R 设置为引导组合键(也可以自己指定,语法如上文所述)。以后,只要先按下 Control-R,然后按下需要重复的次数,最后按要重复的组合键,就可以连续「开火」了。

在下面的例子中,我们使用 Control-R 引导,分别重复了 5 次 Control-N(向下一行)和 10 次 Control-K(删除到行尾)操作。


改出花样

既然默认组合键是以配置文件的形式储存的,一个自然的想法就是:能不能自定义这些组合键配置呢?

当然可以。正如苹果在文档中所说,用户可以通过在 ~/Library/KeyBindings/ 创建一个名为 DefaultKeyBinding.dict 的 plist 文件,自定义新的组合键(或者覆盖自带组合键)。

劝退说在前:我并不建议花很多时间去折腾 Cocoa 文本组合键,只推荐应该优先记住一些用着顺手的默认组合。原因在于,Cocoa 文本组合键本身有很多限制,特别是它在系统中的优先级过低。

怎么个低法?当用户按下一个组合键(称为一个「按键事件」)时,操作系统会按照一定的层级顺序决定由谁来响应这个「按键事件」。在 macOS 中,这个顺序一般是 (1) 操作系统全局、(2) 当前应用程序、(3) 当前窗口和 (4) 文本视图。

Key-event processing

Cocoa 文本系统管理的只是最后一层文本视图。所以,只有当一个组合键在前三个层级都没有收到响应时,才轮得到它出场。相反,如果同一个组合键在这个流程中被「捷足先登」,也就没有 Cocoa 文本组合键什么事了。

这就是为什么前文一直在用略显老气的「文本编辑」app 做演示:这是 macOS 中最经典、最标准的 Cocoa 文本编辑环境,可能没有之一。很多更现代的文本编辑工具要么使用了非标准、非原生的文本视图,要么自定义了很多与默认设置冲突的文本编辑组合键,这都会导致 Cocoa 文本组合键没有用武之地。

除此之外,Cocoa 文本组合键还有设置麻烦、不能同步、与中文输入法兼容性不佳等缺陷;与 Emacs Lisp、VimScript 等更完善的配置语法相比,它缺少条件判断等基础能力,很难实现什么特别强大的效果。

当然,提这些缺陷并不是为了完全否认自定义 Cocoa 文本组合键的价值,只是为了帮助读者确立正确的的效果预期,以及找到合适的使用场景。实际上,包括 Safari 和 Chrome 中大多数网页输入框在内的常见位置,Cocoa 文本组合键都是可用的,做一些轻度配置能很好地提升输入效率。

言归正传,用来设置自定义组合键的 ~/Library/KeyBindings/DefaultKeyBinding.dict 可以使用 XML 或者 NextSTEP 语法;考虑到 XML 的语法过于啰嗦,实践中一般用后者。

会员专属文章,欢迎加入少数派会员。
优质内容
权益周边
会员社群
power+
评论区
精彩评论0
成为少数派会员方可评论,立即加入 。若已是少数派会员,点击登录
还没有评论,来发表第一个评论吧
精彩评论
还没有评论,来发表第一个评论吧
成为少数派会员方可评论,立即加入 。若已是少数派会员,点击登录
会员新功能
内容侧边栏
点击这里拉开侧边栏,即可查看会员内容列表,快速切换内容。