掌握emacs

Table of Contents

1. 第一章

1.1. 引言

我正在使用 Linux. 一个 emacs 用来与 Intel 硬件通信的库.

– Erwin, #emacs, Freenode.

1.1.1. 感谢

感谢阅读<精通 Emacs>(Mastering Emacs). 这本书酝酿已久. 我在 2010 年创建博客 Mastering Emacs, 是受一位好友 Lee 的推荐, 他建议我分享我对 Emacs 及 Emacs 工作流程的看法.

当时, 我在一个名为 blogideas.org 的 org mode 文件中积累了大量但杂乱的想法和概念, 这些都是我学到的并且希望曾有人教过我的东西. 那个文件的最终成果就是这个博客以及现在的这本书.

  1. 特别感谢

    我想感谢以下人士的鼓励, 建议, 提议和批评:

    Akira Kitada, Alvaro Ramirez, Arialdo Martini, Bob Koss, Catherine Mongrain, Chandan Rajendra, Christopher Lee, Daniel Hannaske, Edwin Ong, Evan Misshula, Friedrich Paetzke, Gabriela Hajduk, Gabriele Lana, Greg Sieranski, Holger Pirk, John Mastro, John Kitchin, Jonas Enlund, Konstantin Nazarenko, Lee Cullip, Luis Gerhorst, Lukas Pukenis, Manuel Uberti, Marcin Borkowski, Mark Kocera, Matt Wilbur, Matthew Daly, Michael Reid, Nanci Bonfim, Oliver Martell, Patrick Mosby, Patrick Martin, Sebastian Garcia Anderman, Stephen Nelson Smith, Steve Mayer, Tariq Master, Travis Jefferson, Travis Hartwell.

    和许多人一样, 我在对 Emacs 一无所知的情况下闯入了它的世界; 对我而言, 那是在我大学一年级的时候, 当地的计算机社团主要由 Vim 用户组成. 有人明确地告诉我, "你就用 VIM" 我不想被人指手画脚, 于是选择了 Vim 的极端对立面, 用起了 Emacs.

    那些年里, Emacs 是一款稳定可靠的编辑器, 但却很难上手. 尽管有详尽的用户文档, 但它从未帮助我学习和理解 Emacs.

1.1.2. 2022 版更新

Emacs 28 是目前可用的最新版本, 相比 Emacs 27 有了显著升级, 引入了期待已久的本地编译 (native compilation) 功能. 随着第三方软件包的体积和功能每年都在增长, Emacs 的性能也必须跟上.

本地编译通过将 elisp 编译成适合的平台和系统架构的本地代码, 全面提升了 Emacs 的性能. 这项艰巨的工作主要由 Andrea Corallo 一人完成, 历经数年努力才最终进入 Emacs 28.

除了这个主要特性外, Emacs 28 中的绝大多数改动都是增量式的, 我已相应地更新了本书内容, 当我认为新的命令或定制值得了解时便会提及.

然而, 我想在此强调几个较大的特性:

  1. 项目管理改进

    其中一些改进已在 Emacs 27 中实现, 但在 Emacs 28 中得到了极大增强. 专属的项目管理快捷键映射使其更易访问和使用, 并且增强了 Emacs 中已有的许多功能.

  2. 更友好的帮助和描述命令

    Emacs 是自文档化的, 但确实存在一些我认为此版本有助于弥补的盲点. 帮助系统略有更新, 增加了额外的按键绑定和更完善的描述.

    describe system 是在 Emacs 中查找符号的主要方式, Emacs 28 新增了几个命令来辅助该功能.

  3. 更好的可发现性

    查找与某个辅模式或主模式相关的有用命令并非易事. 一个模式可能会引入许多命令, 而其中只有少数几个可能对普通用户有用. Emacs 现在允许请求执行与当前缓冲区中活动模式相关的命令. 这极大地提高了可发现性.

    至于本书本身, 我也审阅并移除了大部分版本标记–有些甚至可以追溯到 Emacs 24 –因为它们对今天的读者来说不会产生实质性差异. 在此过程中, 我重新措辞并阐明了一些我认为不够明显或清晰的内容.

1.1.3. Emacs 的未来

那么, Emacs 在未来几年会有什么发展呢? 一个诱人的可能性是认识到正则表达式–大多数编辑器用于语法高亮和提取语义信息的首选工具–几十年前就已达到顶峰, 如果我们想要更智能的工具, 就需要改进解析源代码的方式.

这不是一个新颖的想法, 但却难以大规模实现–这一事实阻碍了智能代码补全的发展, 直到 Language Servers 通过一个任何人都可以接入和构建的标准化协议将其大众化.

Emacs 确实有一些主模式使用语言解析器进行语法和语义增强, 例如 js2-mode 和 nxml-mode. CEDET, 一个庞大的多语言 IDE, 拥有自己的语言解析器和语义工具, 大约十年前被合并到 Emacs 核心中, 但从未流行起来.

我相信 tree sitter 1, 一个通用的增量式语言解析库, 已经在其他编辑器中得到了广泛采用, 是完成这项工作的正确工具.

它不会完全取代正则表达式, 也不应该如此: 正则表达式有其用武之地. 但是, tree sitter 已经支持数十种语言, 并且恰如其分地使用了一种基于 S-表达式的查询语言.

这使其非常适合 Emacs 和 elisp. 它速度也很快, 并且可以处理损坏的源代码, 因此在你编写代码时仍能正常工作. 如果你热衷于实验, 现在就可以在 Emacs 2 中使用 tree sitter 来获得大幅改进的语法高亮体验.

1.1.4. 2020 版更新

自从我开始写博客, 分享我对 Emacs 中那些我认为值得人们关注的领域的详细文章以来, 已经过去了十年. 而我出版这本书的第一版也已经五年了.

在那五年里, 各种文本编辑器横空出世并迅速流行起来, 却又在更新的挑战者面前逐渐式微. 与此同时, Emacs 用户仍然在使用 Emacs, 并从其他编辑器的进步 (和退步……) 中学习经验. 但在过去的五年里, Emacs 用户也享受到了许多进步带来的好处.

Microsoft VSCode 因标准化了软件社区几十年前就应该商定并实现的一项内容而值得特别提及: 一种协议, 用于暴露–或者在不可能的情况下重新实现–编译器, 解释器和其他可编程引擎的 内部机制, 其格式使 Emacs 这样的工具能够支持任何使用过闭源 IDE 的人都熟悉的高级功能: 自动重构; 语法和错误高亮; 代码补全; 文档查找等等.

微软, 或许令一些人惊讶, 凭借 Language Server Protocol (一个开放的 JSON-RPC 标准) 赢得了这场论战. 在此协议出现之前, 大多数 IDE 和编辑器都有各自封闭的, 自行开发的实现, 其广度, 深度和质量各不相同.

这对 Emacs 及其用户来说是一大胜利: 他们将受益于构建这些 Language Servers 的人们的集体成果.

  1. Emacs 中的 Language Server

    值得注意的实现有两个: LSP mode3 和 EGlot4. LSP mode 提供开箱即用的完整 IDE 体验, 而 EGlot 则更倾向于一种简约的, 以 Emacs 为中心的方法. 你应该两者都尝试一下, 选择你喜欢的那一个.

1.1.5. 目标读者

当你已经购买了这本书时, 谈论目标读者有点奇怪. 但无论你的 Emacs 水平如何, 提及这一点仍然有意义, 因为你总能从这本书中学到一些东西.

第一个 (也是最明显的) 读者群体是 Emacs 新手. 如果你以前从未使用过 Emacs, 希望这本书能让你豁然开朗.

然而, 如果你是 Emacs 新手且不具备技术背景, 那么学习过程会更艰难一些.

Emacs 虽然远不止用于编程, 但其目标用户主要是精通计算机的人. 尽管完全有可能在不具备技术背景的情况下使用 Emacs, 但本书会假设你具备一定的技术倾向, 但不一定是程序员.

如果你以前尝试过 Emacs 但放弃了, 那么我希望这本书能说服你坚持下去. 但如果你做不到也没关系; 有些语言或环境 (与许多 Emacs 用户的说法相反) 并不适合 Emacs.

如果你主要是一名使用 Visual Studio 的 Microsoft Windows 开发者, 那么使用 Emacs 将会是进两步退一步的情况: 你获得了前所未有的文本编辑和工具集成能力, 但会失去统一 IDE 带来的一些好处.

如果你是 Vim"难民", 那么欢迎来到"黑暗面" ! 如果你的主要目标是使用 Emacs 的 Vim 模拟层, 那么这本书的某些部分是多余的; 它关注的是默认的 Emacs 按键绑定, 并教授" Emacs 方式" 做事.

但不用担心: 本书中的许多技巧和建议仍然适用, 而且谁知道呢–也许你最终会放弃 Evil mode.

最后, 如果你是一位现有 Emacs 用户, 但在提升水平方面遇到困难, 或者只是需要一个"从头开始" 的复习课程, 那么这本书也适合你.

1.1.6. 你将学到什么

在一本书中涵盖 Emacs 的所有内容将是一项西西弗斯式的任务. 相反, 我的目标是教你高效使用 Emacs 所需的知识, 这只是 Emacs 功能的一小部分.

希望在读完这本书并通过实践后, 你能对 Emacs 有足够的了解, 从而能够自己寻找并解答有关编辑器的问题.

更具体地说, 我将从广义上教你六件事:

  • Emacs 是什么: 详细解释 Emacs 使用的重要术语和约定, 这些在很多情况下与其他编辑器大相径庭. 你还将了解 Emacs 的哲学是什么, 以及为什么一个文本编辑器甚至会有一种哲学. 我还会简要谈论 Vim 和编辑器大战 (Editor Wars), 以及那些不同的按键到底是怎么回事.
  • 开始使用 Emacs: 如何安装 Emacs, 如何运行它,以及如何确保你使用的是一个相对较新版本的 Emacs. 我会解释如何修改 Emacs 以及如何使你的更改永久生效. 我将介绍 Customize 界面以及如何加载颜色主题. 最后, 我会谈谈 Emacs 的用户界面以及一些在你遇到困难时的实用技巧.
  • 发现 Emacs: Emacs 是自文档化的; 但这意味着什么, 你如何利用这一点来更多地了解 Emacs 或回答你关于特定功能的问题? 我将展示当我要学习如何在 Emacs 中使用新模式或功能时 我会做什么, 以及你如何利用 Emacs 的自文档特性来找到你正在寻找的东西.
  • 移动:如何在 Emacs 中移动. 乍一看很简单, 但在 Emacs 中, 有很多方法可以用最少的按键从当前位置到达目标位置. 对于开发者来说, 快速移动可能是成功的一半, 知道如何快速移动会让你更高效. 你将学到的一些内容包括: 按语法单元移动, 以及语法单元到底是什么; 使用窗口和缓冲区; 搜索和索引文本; 选择文本和使用标记.
  • 编辑: 与移动章节一样, 我将向你展示如何使用 Emacs 提供的各种工具来编辑文本. 这包括按平衡表达式, 单词, 行, 段落编辑文本; 创建键盘宏以自动化重复任务; 搜索和替换; 寄存器 ; 多文件编辑; 缩写; 远程文件编辑等等.
  • 生产力: Emacs 不仅仅能编辑文本, 这一章只是初步展示了吸引如此多人使用 Emacs 的原因: 它与数百个外部工具的紧密集成. 我会激发你的兴趣, 并向你展示当你将 Emacs 的移动和编辑 功能结合起来时可以做的一些更有趣的事情.

2. 第二章 Emacs 之道

窗口系统的目的是在你那唯一的, 万能的 emacs 窗口周围放一些有趣的装饰.

– Mark, gnu.emacs.help.

如果你将现代计算时代的开端设想在 20 世纪 60 年代, 那么 Emacs 几乎比其他任何东西都存在得更久.

它最初由 Richard Stallman 于 1976 年在另一个名为 TECO 的编辑器之上编写的一组宏6.

TECO 现在主要因其极其晦涩难懂而被人记住. 从那时起, 出现了许多相互竞争的 Emacs 实现, 但如今你可能只会遇到 XEmacs 和 GNU Emacs.

2.0.1. 指导思想

Emacs 是一款为 Tinker 打造的编辑器. 简单明了. 那些在 Emacs 上进行修改的人这样做是因为它的几乎每一个方面都是可扩展的. 它是最初的可扩展, 可定制, 自文档化的编辑器. 如果你来自其他文本编辑器, 能够改变任何东西的想法似乎会让你从工作中不必要地分心–的确, 很多 Emacs 的修改确实是以牺牲实际工作为代价的–但是一旦你意识到你可以塑造 你的编辑器来做你想做的事情, 它就打开了一个充满可能性的世界.

这意味着你可以真正地将 Emacs 的所有按键重新绑定到你喜欢的方式; 你不受 IDE 未文档化且充满错误的 API 的束缚, 也不受如果你确实进行了更改所带来的限制–例如你的自 定义导航键在搜索替换窗口或内部帮助文件中不起作用. 确实, 在 Emacs 中, 你可以改变一切–而且人们确实这样做.

Vim 用户正在迁移到 Emacs, 因为, 嗯, Emacs 通常比 Vim 本身更好用.

Emacs 会吸引你. 一旦你开始使用 Emacs 进行编辑, 你会意识到使用 Emacs 进行 IRC, 电子邮件, 数据库访问, 整理思绪, 命令行 shell, 编译代码或上网和编辑文本一样容易–而且你可以 保留你的按键绑定, 主题以及 Emacs 和 elisp 的所有强大功能来配置或改变一切的行为.

当一切都无缝地结合在一起时, 你就避免了从一个应用程序切换到另一个应用程序时常见的上下文切换: 大多数 Emacs 用户除了编辑器, 一个浏览器和可能一个专用的终端应用程序之外, 几乎不使用其他东西.

Emacs 的历史

Emacs 的源代码仓库 (现在是 Git) 可以追溯到 30 多年前, 拥有超过 130,000 次提交和近 600 名提交者.

如果你想修改 Emacs, 或者修改众多可用包中的任何一个, 你需要编写 Emacs Lisp (也非正式地称为 elisp). 曾有一些尝试将其他语言嫁接到 elisp 和 Emacs 上, 但都没有持久的效果.

事实证明, LISP 对于像 Emacs 这样非常高级的工具来说, 实际上是一个完美的抽象. 而且大多数现代语言不一定能经受住时间的考验: 90 年代曾短暂考虑过 TCL, 因为它当时很流行–但 如今它比 LISP 更加晦涩难懂.

唯一的缺点是, 修改 Emacs 配置是你必须学会适应的事情 (而且还是用 LISP, 但正如我在下一部分解释的那样, 这实际上是一件好事). 这就是为什么我强调它是一款为 Tinker 打造的编辑器. 如果你讨厌修改任何东西, 并希望一切开箱即用, 你还剩下两个选择:

  • 使用入门套件: 有许多免费的入门套件, 它们配备了额外的软件包和作者认为合理的默认设置. 它们是入门的好方法, 但缺点是你不知道 Emacs 在哪里结束, 入门套件的附加功能从哪里开始. 我推荐你看看以下入门套件之一:

    如果你想要一个带有浓厚 Vim 倾向的, 有主见的套件:

  • 使用默认设置: 当然是一个选项, 但 Emacs 自带的是中性但过时的默认设置. 你需要根据自己的喜好配置 Emacs, 或者让别人为你完成这项工作. 对于一个与主流编辑器如此截然不同的编辑器 来说, 维护者在更改默认设置方面持保守态度, 因为担心会惹恼那些 (在所有人中, 最应该知道如何配置 Emacs 的) 老用户.

就我个人而言, 我从未使用过入门套件–在我将近 20 年前开始使用时, 它们并不像现在这样存在–而是大量借鉴了其他人当时的 .emacs 文件.

这种方法非常适合那些想要端到端了解其编辑器的人. 我建议你看看入门套件–参考前面提到的那些套件的好点子–并从中自由借鉴.

2.0.2. LISP?

Emacs 由其自身的 LISP 实现驱动, 称为 Emacs Lisp 或简称 elisp. 许多人被这种深奥的语言吓倒或望而却步; 这很可惜, 因为在一个围绕 LISP 作为单一机器理念构建的编辑器中学习 LISP 是一种实用且有趣的方式. Emacs 的每个部分都可以被检查, 求值或修改, 因为编辑器大约 95% 是 elisp, 5% 是 C 代码. 这也是学习一种激进范式的实用方法: 代码和数据是可互换和可塑的; 由于其简单的语法, 语言可以通过宏轻松扩展.

不幸的是, 学习 elisp 在某种程度上是不可避免的. 在本书中, 我将讨论 Customize 界面: 一个动态生成的, 可定制选项的界面. 然而, 像重新绑定按键这样简单的事情也意味着你需要与 elisp 交互. 但这并非全是坏事. 你可能遇到的大多数问题很久以前就有人解决了; 只需要在互联网上搜索解决方案即可.

尽管 elisp 相对于 Python, Ruby 和 JavaScript 等更" 现代" 的语言相对不受欢迎, 但我怀疑如果使用更传统的命令式/面向对象语言, Emacs 是否会拥有同样强大的可扩展性. LISP 之所以如此 出色, 是因为源代码和数据结构本质上是同一回事: 你作为人类阅读的 LISP 源代码几乎与 LISP 将代码作为数据结构操作的方式相同–" 什么是数据?" 和 " 什么是代码?" 这两个问题之间的区别是零.

数据即代码, 宏系统以及"劝告"(advise) 任意函数的能力–意味着你可以在不复制和修改原始代码的情况下修改现有代码的行为–为你提供了前所未有的能力来根据你的需求改变 Emacs. 在大多数软件项目 中被认为是代码异味或糟糕架构的东西, 在 Emacs 中实际上是一个主要优势: 你可以挂接, 替换或更改 Emacs 中的现有例程以满足你的需求, 而无需重写大量他人的源代码.

本书不会详细教授 elisp: Emacs 有一个内置的 elisp 介绍8, 如果你好奇–老实说你应该好奇–我强烈推荐它. LISP 很有趣, 这是学习和使用一种强大语言的好方法.

2.0.3. Emacs 即操作系统

nov.png

Figure 1: 在 EPUB 阅读器 nov 中显示的 Mastering Emacs 书籍截图

Emacs 就像一个喜鹊巢穴, 堆满了闪亮的东西. 如果你是 Emacs 新手, 你可能会觉得我有点夸大其词, 但请考虑一下, Emacs 自带了一个内置的屏保程序 (M-x zone); 一个文字冒险游戏 (M-x dunnet); 一个 M-x tetris 克隆; 一个功能齐全的客户端-服务器模型; 一个月相计算器; 一个 M-x doctor 中的心理治疗师; 几个电子邮件客户端; 一个用于绘制 ASCII 艺术的艺术家模式; 一个名为 EXWM 的基于 Emacs 的 X Window 管理器; 当然, 还有一个可以显示这本书的 EPUB 阅读器 nov.

当你运行 Emacs 时, 你实际上启动了一个小型的 C 核心, 负责与操作系统的 ABI 进行底层交互. 这包括诸如文件系统和网络访问, 在屏幕上绘制内容或向终端打印控制代码等普通任务.

然而, Emacs 的基石是 elisp 解释器–没有它, 就没有 Emacs. 这个解释器有些陈旧; 它正在努力满足用户日益增长的需求.

现代 Emacs 用户对他们简陋的解释器期望很高: 速度和异步是两个主要问题. 解释器在单线程中运行, 密集的任务会锁定 UI 线程.

不过, 也有变通方法. 这些问题, 尽管很多, 并没有阻止人们编写越来越复杂的软件包.

随着 Emacs 28 的发布–并且如果你的 Emacs 实例编译时支持它–你使用的所有 elisp 代码都会无缝编译成本地代码, 从而显著提高速度.

当你编写 elisp 时, 你不仅仅是在沙盒中运行与一切隔离的代码片段–你正在改变一个活生生的系统; 一个运行在操作系统之上的操作系统.

你修改的每个变量和你调用的每个函数都由你在编辑文本时使用的同一个解释器执行.

Emacs 是黑客的梦想, 因为它是一个巨大的, 可变的状态. 它的简单性既是福也是祸. 你可以重新定义活动函数; 随意更改变量;

并且可以随时查询系统的状态–这个状态会随着 Emacs 响应从键盘到网络堆栈的每个按键事件而改变. Emacs 是自文档化的, 因为它就是文档本身. 没有其他编辑器能做到这一点. 没有编辑器能与之匹敌.

2.0.4. 可扩展性

然而 Emacs 从不崩溃–至少不常崩溃. Emacs 有一个正常运行时间计数器 (M-x emacs-uptime) 来证明它并非如此–数月的正常运行时间并不少见.

因此, 当你向 Emacs 提问时–正如我稍后将向你展示的那样–你实际上是在询问你的 Emacs 当前的状态是什么. 因此, Emacs 拥有出色的 elisp 调试器 , 并且可以无限制地访问 Emacs 自身解释器和状态的各个方面–因此它也拥有出色的代码补全功能. 任何时候你遇到一个 LISP 表达式, 你都可以让 Emacs 对其求值, 它就会这样做: 从数字相加到设置变量再到下载软件包.

可扩展性很重要, 但如果你不知道 Emacs 中可能性的范围, 就很难强调其重要性. 我在这里仅列举了几个 Emacs 可以做什么的例子–或者更重要的是, Emacs 可以让人们做什么.

  • 面向盲人的语音界面: 25 年来, Emacspeak9 为失明或视力受损的 Emacs 用户提供了一种通过语音界面与 Emacs 和世界互动的方式, 该界面能够理解屏幕上显示的内容. Emacspeak 会改变语音引擎的语音特征以反映源代码中的不同语法元素, 或强调布局, 字体或图形图标.

    对于失明的 Emacs 用户来说, Emacspeak 是一条生命线, 使他们能够通过使用 Emacs 的许多工具 (例如电子邮件或网页浏览) 继续工作.

    这个功能已经存在了 25 年, 这本身就令人印象深刻, 但 Emacs 支持这种变革性软件的能力更是令人鼓舞.

  • 远程文件编辑: Emacs 的 TRAMP10 (Transparent Remote (file) Access, Multiple Protocol) 可以让你无缝地编辑远程文件, 使用各种网络协议, 包括 SSH, FTP, Docker , rclone, rsync 等等, 就好像这些文件在本地一样.
  • Shell 访问: Emacs 有一个内置的, 支持 ANSI 的终端模拟器; 一个围绕诸如 bash 之类的 shell 的 Emacs 包装器; 以及一个完全用 elisp 编写的, 功能齐全的 shell, 称为 Eshell.
  • ORG mode: 一个待办事项, 日程安排, 项目规划, 文学编程, 笔记 (以及更多!) 应用程序. 它被广泛认为是迄今为止最好的基于文本的组织工具–这一成就只有人们仅仅为了使用它而切换 到 Emacs 这一事实才能超越.
  • 符号计算器: 支持符号代数, 任意精度计算, 自定义函数, 矩阵和基于单位的数学运算等等的反波兰表示法计算器.
  • 音乐播放器: Emacs 多媒体系统 (EMMS) 是一个交互式媒体浏览器和音乐播放器.

2.0.5. 重要约定

在我们继续之前, 我需要谈论一些重要的 Emacs 约定. 你务必记住它们, 或者至少在你有所怀疑时参考这一页. 它们会在本书和其他地方反复出现, 如果你想利用 Emacs 广泛的内部文档, 了解它们至关重要. 这并非 Emacs 或本书中使用的所有约定的详尽列表. 我将在全书中介绍特定的术语和概念, 不过有些术语超越了特定主题, 因此提前了解它们很重要.

2.0.6. 缓冲区 (Buffer)

info.png

Figure 2: Emacs 界面, 上方窗口显示 Info 手册, 下方窗口使用 DocView 显示 PDF 文档.

大多数文本编辑器和 IDE 都是基于文件的: 它们显示文件中的文本, 并将文本保存到文件中. 仅此而已.

在 Emacs 中, 所有文件都是缓冲区, 但并非所有缓冲区都是文件. 如果你想用一个临时区域来存储日志文件中的片段, 或者处理文本, 或者无论出于什么原因–你只需创建一个新的缓冲区并为其命名. Emacs 不会强迫你提供文件名. 该缓冲区将存在于 Emacs 中, 并且仅存在于 Emacs 中. 你必须显式地将其保存到磁盘上的文件中才能使其持久化.

Emacs 使用这些缓冲区不仅仅是为了编辑文本. 它还可以充当 I/O 设备并与另一个进程通信, 例如像 bash 甚至 Python 这样的 shell.

几乎所有 Emacs 自身的命令都作用于缓冲区. 因此, 当你让 Emacs (例如) 搜索和替换时, 它实际上会在一个缓冲区上进行搜索和替换–可能是你正在编写的活动缓冲区, 或者可能是一个临时副本– 而不是像你可能想的那样在一个不透明的内部数据结构上进行操作.

在 Emacs 中, 缓冲区就是数据结构. 这是一个极其强大的概念, 因为你在 Emacs 中用于移动和编辑的命令几乎总是与你在 elisp 幕后使用的命令相同. 因此, 一旦你记住了 Emacs 自己的用户命令 , 你就可以在简单的函数调用中使用它们来模拟你手动执行的操作.

2.0.7. 窗口 (Window) 与框架 (Frame)

当你在屏幕上查看缓冲区时, 它显示在一个窗口中. 但在 Emacs 中, 窗口只是框架的一个平铺部分, 而框架是大多数窗口管理器所谓的窗口. 在 Emacs 中, 情况正好相反; 是的, 这非常令人困惑.

如果你看上面的截图, 你会看到两个窗口和一个框架. 每个框架可以有一个或多个窗口, 每个窗口只能有一个缓冲区.

因此, 缓冲区必须在窗口中查看才能显示给用户, 而窗口必须在框架中才能对用户可见.

把它想象成一个物理窗户有一个窗框, 每个窗框由窗格组成.

在 Emacs 中, 你可以随意创建任意数量的框架, 并且在每个框架中, 你可以自由地将该框架拆分和平铺成多个窗口. 如果你使用大屏幕显示器 (如今谁不使用呢?), 使用 Emacs 的平铺系统在屏幕上显示多个缓冲区会非常有益.

2.0.8. 模式行 (Modeline), 回显区 (Echo Area) 与微缓冲区 (Minibuffer)

mode-line.png

Figure 3: 终端 Emacs 会话示例, 显示模式行, 回显区和微缓冲区.

上图是终端 Emacs 会话的一个示例. Emacs 使用模式行来传达有关 Emacs 和你所在缓冲区的信息. 模式行如下所示:

-UUU:**--F3 *scratch* All L4 (Lisp Interaction) --

在一个相当小的区域内传达了大量信息. 你首先应该关心的是名称和模式. 在本例中, 缓冲区名为 scratch, 主模式是 Lisp Interaction. 大多数编辑器都有类似的概念, 称为状态栏.

各种可选信息都可以显示在模式行中: 笔记本电脑电池电量, 你所在的当前函数或类, 你正在使用的源代码控制修订版或分支等等.

微缓冲区直接位于模式行下方, 用于显示错误和常规信息:

-UUU:**--F3 *scratch* All L4 (Lisp Interaction) --
M-x insert-hello-world

在本例中, 我触发了 Emacs 的扩展命令 (extended command) 功能–由 M-x 符号表示, 这是我将在关于按键的章节中讨论的一个概念–并且我已在 M-x 提示符后输入了命令 insert-hello-world.

回显区和微缓冲区共享屏幕上的同一位置. 微缓冲区几乎与普通缓冲区相同: 你可以使用大部分编辑命令, 单行微缓冲区会在必要时扩展到多行.

它是你与 Emacs 通信的方式: 如果你想搜索一个字符串, 你就在微缓冲区中写入你想搜索的字符串. 它支持各种复杂的补全机制来帮助你找到你需要的东西, 是一个你会经常使用的工具.

2.0.9. 点 (Point) 与标记 (Mark)

点 (point) 只是插入符号 (caret) 或光标 (cursor) 的另一种说法. Emacs 文档在 point 和 cursor 的使用上相当不一致; 你会看到两者都出现.

尽管如此, point 本身就是你在缓冲区中的当前位置. 在本书中, 我将使用 █ 来表示 point.

每个缓冲区分别跟踪 point 的位置, 因此如果你在缓冲区之间切换, 每个 point 的位置都会被分别记住.

在 Emacs 中, 我们经常谈论" 当前缓冲区" (current buffer), 这可能意味着两件事–目前我们只对其中一件感兴趣–那就是 point 所在的缓冲区 (另一种情况基本相同, 但涉及在 elisp 中以编程方式更改缓冲区).

拥有 point 的缓冲区是当前缓冲区, 因为它是你写入和移动的缓冲区. 任何时候都只能有一个缓冲区是当前缓冲区, 并且正是这个缓冲区拥有 point.

在 Emacs 中, point 的作用不仅仅是作为你键入字符在屏幕上最终位置的可视标记. 它也是称为 point 和 mark 的组合的一部分.

point 和 mark 表示区域 (region) 的边界, 区域是通常在当前缓冲区中的连续文本块. 在其他编辑器中, 它被称为选区 (selection) 或高亮 (highlight).

大多数编辑器没有为区域的开始和结束指定名称, 但在 Emacs 中我们有, 在"选区和区域"中我将更多地讨论其原因.

历史上, Emacs 不会在屏幕上显示可见区域, 而是需要你凭空想象. Emacs 支持可见区域已经很长时间了, 称为 transient mark mode (或简称 TMM). 它默认是启用的. 令人惊讶的是, 完全不使用 TMM 也有一些价值, 但我稍后会详细讨论这一点.

但就像 point 一样, mark 的作用也比看起来要多. 它是区域的边界, 是的, 但它也是一个信标, 你可以从缓冲区的其他部分返回到它. mark 通常是不可见的.

2.0.10. Kill, Yank 与 CUA

对初学者来说, 第一个–也许是最令人反感的–偏离事实上的用户界面标准的做法是 Emacs 的剪贴板系统. 剪切, 复制和粘贴几乎被普遍认为是 Ctrl+x 或 Shift+Delete; Ctrl+c 或 Ctrl+Insert; 以及 Ctrl+v 或 Shift+Insert.

在 Emacs 中, 按键和术语有很大不同: killing 是剪切; yanking 是粘贴; 复制则笨拙地称为 saving to the kill ring (或非正式地称为 copy).

原因和以前一样, 都是历史性的. 大多数按键和术语源于 IBM 的 Common User Access (CUA)11 和 Apple. 但 CUA 是在 1987 年引入的, 比 Emacs 确定自己的术语和标准要晚很多年.

2.0.11. .emacs.d, init.el 与 .emacs

Emacs 用户最喜欢的消遣之一就是与其他 Emacs 黑客分享那些能让他们的生活更轻松的小代码片段或定制.

历史上, 这些设置保存在一个名为 .emacs 的文件中, 但大多数人现在将他们的定制保存在 Linux 上的 ~/.emacs.d/init.el 和 Windows 上的 %HOME%\init.el 中.

由于 Emacs 现在会向你的文件系统写入更多文件, 它们被保存在一个名为 .emacs.d 的目录中, 以避免弄乱你的主目录.

Emacs 27 中的 XDG 支持

Emacs 27 及更高版本现在支持 XDG 约定, 即在支持该约定的 Linux 平台上将用户配置存储在 ~/.config/emacs/init.el 中.

所以, 当人们谈论他们的 init file, 或者他们的 ".emacs file" , 或者如果他们让你把某些东西放到那个文件中, 他们指的就是这个. 如果你是 Emacs 新手, 你应该使用 ~/.emacs.d/init.el. 当你向该文件添加某些内容时, 你需要告诉 Emacs 运行它. 有很多方法可以做到这一点, 我将在" 求值 Elisp 代码" 中解释如何做, 但我对初学者的首选建议是关闭 Emacs 并重新启动它.

Emacs 中的入门套件现在非常普遍. 它们是 Emacs 的社区附加组件, 捆绑了许多更改甚至整个第三方软件包, 如果你使用其中一个, 你应该阅读它们的文档以了解存储自己更改的最佳实践.

Emacs 不会为你保存更改. 如果你想让 Emacs 保留更改, 你必须通过 Customize 界面来完成. 这意味着你有责任将想要保留的更改保存到 init.el. 同样, 如果你犯了错误并在 Emacs 中弄坏了某些东西, 或者如果你做了不想要的更改, 只需退出并重新启动 Emacs.

2.0.12. 主模式 (Major Modes) 与辅模式 (Minor Modes)

Emacs 中的主模式控制缓冲区的行为方式. 因此, 如果你想编辑 Python 代码并且在 Emacs 中访问一个名为 helloworld.py 的文件, 那么 Emacs 将通过一个将文件扩展名映射到主模式的集中式注册表知道这是一个 Python 文件, 并且应该使用 Python 主模式. 每个缓冲区总是会有一个主模式. 主模式可能是基本的, 不提供字体锁定 (语法高亮) 和特定功能, 或者它可能是完全相反的, 引入字体锁定, 高级缩进引擎和专用命令.

Font Locking 是 Emacs 中语法高亮的正确术语, 它又由一系列属性 (颜色, 字体, 文本大小等) 的 faces 组成, 字体锁定引擎使用这些 faces 来美化文本的打印效果.

Emacs 的术语 face 和 font lock 早于你在其他地方看到的更常用术语.

你可以随时通过键入另一个主模式的命令来更改缓冲区的主模式. 除了 Emacs 的文件扩展名和相关主模式的注册表之外, 还有一个针对具有模糊 (或没有) 文件扩展名的文件的系统: Emacs 会扫描文件的第一部分并尝试推断主模式. Emacs 很少会出错, 你需要更改它.

重要的是要记住每个缓冲区只能有一个主模式. 相比之下, 辅模式通常是可选的附加组件, 你可以为部分 (或全部) 缓冲区启用它们. 一个例子是 flyspell mode, 这是一个在你书写时检查文本拼写的辅模式.

主模式总是显示在模式行中. 一些辅模式也显示在模式行中, 但通常只有那些会改变缓冲区或你与之交互方式的辅模式才会显示.

3. 第三章 初步入门

我使用 Emacs, 它可以被认为是一个热核文字处理器.

– Neal Stephenson, <In the Beginning… was the Command Line>.

3.0.1. 安装和启动 Emacs

在我深入讲解安装 Emacs 的细节之前, 你应该检查一下它是否已经安装了. 然而, 如果已经安装了, 你必须格外小心: 它可能是一个非常旧的版本.

检查 Emacs 的版本 你可以通过键入 emacs –version 来检查 Emacs 的版本.

截至 2022 年, 最新的主要版本是 GNU Emacs 28. 我建议你尽快采用最新版本.

你应该尽量保持最新版本: Emacs 的主要版本发布频率不高, 因此跟上更新应该不会给你带来太多麻烦. 如果你确实升级了, 很少是为了修复错误 (因为 Emacs 实际上非常稳定), 而是为了新功能以及大多数软件包作者都假设你正在使用最新版本这一事实. (话虽如此, 如果你在一个非常冷僻的平台上, 可能根本无法升级.)

如果你正在使用 XEmacs 或其他非 GNU Emacs, 你真的应该切换. 十五年前, XEmacs 曾一度领先, 但 GNU Emacs 早已追赶上来并超越了 XEmacs 的功能.

Emacs 的一个标志是它长期以来坚信破坏兼容性应该经过多个主要版本, 从弃用到移除. 不常见的是, Emacs 邮件列表中会涌入抱怨, 声称他们在 20 世纪 80 年代末编写的一段代码突然坏掉了, 因为 Emacs 维护者终于移除了一个早已过时的变量或函数.

Emacs 支持你可能使用的大多数主要平台: BSD 和 Linux, Mac OSX, MS-DOS 以及 Microsoft Windows. 我不会详细介绍如何在 Linux 以外的操作系统上编译或构建 Emacs. Emacs 被设计成一个跨平台编辑器, 但如果你不在 Linux 上运行它们, 总会有一些权衡. 特别是 Mac OSX, 关于如何最好地运行 Emacs 似乎有很多相互矛盾的建议; 我能提供的最好建议是尝试几种不同的方法, 找到适合你的那一种.

  • Microsoft Windows: Emacs 在其官方网站上发布了针对 Microsoft Windows 的官方构建版本12. 解压并运行可执行文件即可. 大多数外部工具支持在 Windows 上将无法工作. 像内置的 grep 支持这样的功能需要 GNU coreutils 存在. 然而, 你可以从 Cygwin13 运行 Emacs, 从而在 Windows 上获得一个类似 Linux 的环境. 或者, 交叉编译的 GnuWin3214 项目几乎拥有所有能在 Windows 上本地运行的 Linux 命令行程序. 另一个新出现的机会是使用 Windows Subsystem for Linux, 这是一个兼容层, 可以在 Windows 10 的子系统之上本地运行 Linux.
  • Mac OSX: 一种方法 (尽管有多种) 是使用非官方的 Emacs 构建版本15. 也有 Aquamacs, 但它与 GNU Emacs 有很大不同. 这个主题本身相当复杂. 有些人更喜欢使用像 homebrew 这样的包管理器, 而另一些人则不然. 通常, 使用 homebrew 的人也经常使用 homebrew 版本的 Emacs. EmacsWiki 上关于在 Mac OSX 上安装 Emacs 的文章16 是一个很好的起点, 如果你想自己编译 Emacs.
  • Linux: Emacs 几乎总是存在于你的发行版的包管理器中. 有些发行版更新到新的次要版本较慢 (这些版本很少是次要的, 而是增加了大量新功能和错误修复), 因此从源代码构建可能值得你花时间. 在 Ubuntu 上, 只需 apt-get install emacsNN 即可, 其中 NN 是 Emacs 的主要版本号: 24, 25 等等. 如果你想从源代码构建自己的 Emacs 版本, 我建议你使用 apt-get build-dep emacsNN 来构建和安装 Emacs 的依赖项. 从那时起, 按照构建说明中概述的常规 configure, make, make install 过程操作就很简单了.

3.0.2. 启动 Emacs

启动 Emacs 非常简单, 只需从命令行运行 emacs 即可. 如果你从窗口管理器运行该命令, Emacs 将作为 GUI Emacs 启动–而不是在终端内运行的 Terminal Emacs.

你可以通过添加参数 -nw 来强制 Emacs 在终端中运行, 即使在窗口管理器中也是如此, 例如: emacs -nw.

你可以向 Emacs 传递许多命令行开关, 但入门只需要四个:

Switch Purpose
–help 显示帮助信息
-nw 强制 Emacs 在终端模式下运行
-q 不加载初始化文件 (例如 init.el)
-Q 不加载站点范围的启动文件17, 你的初始化文件, 也不加载 X 资源
  1. Emacs 客户端-服务器

    那么, 当你在命令行中消磨时间却需要编辑一个文件时, 你该如何处理呢? 也许你正在从命令行写电子邮件或写提交信息–你会想用 Emacs, 最好是你已经运行的那个 Emacs 实例. 答案, 忽略 Emacs 对电子邮件和源代码控制系统都有一流支持这一事实, 就是 Emacs 的客户端-服务器模式.

    客户端-服务器功能非常棒, 但在你熟悉 Emacs 基础知识之前, 我不建议花太多时间去研究它.

    Emacs 服务器模式的无数优点是:

    • 持久会话意味着 Emacs 将重用相同的会话, 而不是每次都生成一个新的, 独立的 Emacs 副本.
    • 它与 $EDITOR 配合良好, 通过在共享的 Emacs 会话中打开文件, 并在会话结束时自动向调用程序发送信号.
    • 使用 emacsclient 二进制文件从命令行快速打开文件. Emacs 客户端将连接到本地 Emacs 服务器实例并指示它打开文件.

    有几种激活 Emacs 客户端-服务器模式的方法:

    M-x server-start 会在已运行的 Emacs 实例内部启动一个服务器. 当你键入此命令时, 该实例将变成一个服务器; 本身没有视觉反馈表明它正在运行.

    当退出此 Emacs 实例时, 服务器也将关闭–因此, 如果想要一个服务器守护进程, 需要下面的选项.

    Emacs 28 如果你正在使用窗口管理器, 并且它相对较新, 你也可以运行 Emacs (客户端) 或要求它通过该条目打开文件, 以重用你现有的 Emacs 实例.

    emacs –daemon 会将 Emacs 作为守护进程运行. 它会像上面那样调用 server-start, 但会立即将控制权返还给你的终端并在后台运行, 等待客户端请求.

    如果你的操作系统支持 systemd, Emacs 也自带了对它的原生支持. Emacs 26 及更高版本可以通过运行命令 systemctl --user enable emacs 自动配置 systemd 单元文件.

    然后, Emacs 的守护进程就由 systemd 管理了.

    采用服务器方式, 就不能再使用默认的 emacs 二进制文件了. 该二进制文件只会生成独立的实例. 可以用 emacsclient 来启动客户端.

    将 $EDITOR 环境变量设置为 emacsclient, 一切应该就能正常工作了. emacsclient 二进制文件有自己的一组开关, 了解一下:

    Switch Purpose
    –help 显示帮助信息.
    -c 创建一个图形框架 (如果 X 可用) 或一个终端框架 (如果 X 不可用).
    -nw 创建一个终端框架.
    -n 客户端将立即返回, 而不是等待你保存更改. 如果你只是想打开一堆文件, 这很实用.

    启动一个 emacsclient 实例时, 客户端会等待文件编辑完成. 按 C-x # 将切换到你通过客户端编辑的下一个缓冲区–当你为你打开的文件完成此操作后, Emacs 会向客户端发送信号以退出并将控制权返还给终端.

    如果你正在使用像 git 这样的工具, 它允许你在使用其他编辑器时使用 $EDITOR 来编辑提交信息, git 会等到收到编辑器已将提交信息保存到临时文件的信号后, 再继续执行提交操作.

    如果你希望客户端只打开文件而不等待, 可以添加 -n 开关. 我在进行探索性工作时或者希望文件在 Emacs 中"永久"打开时会使用它.

3.0.3. Emacs 界面

image-page-48.png

Figure 4: Emacs 启动画面 (splash screen).

当你第一次启动 Emacs 时, 你会看到启动画面. 这可能是大多数 Emacs 黑客最先禁用的东西之一, 同时还有滚动条, 菜单栏和工具栏. 在你熟悉 Emacs 之前, 我建议你保持 UI 元素启用, 因为它们会为你提供一种快速访问常用功能的方法, 即使你可能不记得如何手动操作, 尽管它们会占用你屏幕上宝贵的空间.

如果你在终端中使用 Emacs, 你仍然可以通过按 F10 来访问菜单栏.

如果你没有看到类似于上图的用户界面, 很可能是因为你的 init file 中的自定义设置造成的. 最快的测试方法是关闭 Emacs 并用 emacs -q 重新启动它. 如果这样解决了问题, 那么肯定是你 Emacs 中的自定义设置造成的. 大多数入门套件都假设你对 Emacs 相当熟悉, 它们通常会禁用菜单栏和工具栏之类的东西.

你现在可以自由地摆弄 Emacs 了: 箭头键可以正常工作, 再加上菜单栏, 你就可以打开和保存文件了. Emacs 会自动检测大多数文件类型并应用正确的 major mode –如果它做不到, 你可能需要安装第三方软件包, 我稍后会谈到.

3.0.4. 按键 (Keys)

Emacs 中最重要的主题. Emacs 以两件事闻名: 它晦涩的键盘指令和它是万能的厨房水槽式编辑器. 漫画 xkcd18 幽默地引用了 Emacs 传说中的那一部分. 一个更古老的笑话是 Emacs 代表 " Escape Meta Alt Control Shift."

  1. Caps Lock 用作 Control 键

    你应该对你的环境做的最重要的修改之一是将你的 caps lock 键重新绑定为 control. 你会经常使用 control 键, 为了避免 Emacs pinky (因过度使用小指按 Control 键导致的重复性劳损), 我建议你完全解除右 control 键的绑定, 改用 caps lock.

    是的, 这会是一个恼人的转变, 但也是一个值得的转变 (顺便说一句, 这在 Emacs 之外也会对你很有用).

    这种改变是必要的, 因为在旧键盘20上, control 键占据了现在 caps lock 键使用的空间, 所以可以不费力地按到左 control 键.

    1. 自定义和可编程键盘

      如果你拥有一个人体工程学机械键盘–它们通常带有可定制的固件–你可能想更进一步, 避免用小指按所有修饰键. 如果你的键盘有拇指键或分层键的功能, 就像许多高端人体工程学机械键盘那样, 你应该更改键盘布局, 至少让 control 键在你食指或拇指的轻松触及范围内.

3.0.5. M-x: 执行扩展命令

Emacs 中只有一小部分可用命令绑定到了实际按键上. 大多数则没有: 它们很少使用, 不需要按键绑定; 或者你可能已经明确覆盖了它绑定的按键, 使其未绑定; 或者你可能忘记了它的按键绑定.

本质上, 你经常需要运行不常用的命令. 为此, 请按 M-x (发音为 mex, M x, 或 meta x). 在你的微缓冲区 (minibuffer) 中, 会出现一个提示, 你可以自由输入你希望运行的命令的名称.

当 Emacs 用户说诸如" 运行 M-x lunar-phases 来查看月相" 之类的话时, 他们实际的意思是: 按住 meta 并按 x, M-x 提示符会出现在你的微缓冲区中 (即 Emacs 最底部的那一行).

此时, 你可以输入命令的名称. 试试看, 输入 lunar-phases 并按 RET. lunar-phases 命令会在你的屏幕上打开一个新窗口, 显示从今天开始的月相. 你可以按 C-x 1 来隐藏该缓冲区.

提示 如果你不小心输入了 M-x, 记住你可以按 C-g 再次退出.

Emacs 内置了自动补全支持, 所以按 TAB 会打开一个新窗口并列出所有可能的候选项. 当你输入并按 TAB 时, Emacs 会自动缩小候选项列表. 如果在你按 TAB 时, 你部分输入的匹配只剩下一个候选项, Emacs 会为你补全整个名称. 你也可以直接按 RET –它的补全方式与 TAB 相同, 但如果只剩下一个候选项, 它还会额外运行该命令.

你可能认为 M-x 是一个特殊的 Emacs 命令, 但实际上并非如此. 它也同样是用 elisp 编写并绑定到一个按键上的, 和其他所有东西一样.

  1. 命令与函数

    当我谈论命令时, 我指的是用户可以访问的一种函数类型. 一个函数要能被用户访问 (暂且不提在 elisp 中可以求值任何表达式的能力), 它必须是 interactive 的, 这是 Emacs 的一个术语, 指的是具有额外属性的函数, 使其可以通过 execute extended command (M-x) 界面和按键绑定来使用. 因此, 如果你是一个包的作者, 你必须选择某个特定函数是否能通过 M-x 界面被最终用户访问. 将其标记为 interactive 将使其对最终用户可见. 换句话说, 如果它不是 interactive 的, 你就不能从 M-x 运行它, 也不能将它绑定到按键上.

3.0.6. M-S-x: 针对缓冲区的执行扩展命令

当你调用 M-x 时, 你会看到 Emacs 中所有可以执行的命令. 如果你确切地知道要查找什么, 这很好; 如果你不知道, 那就不太好了. 这是一个导致 Emacs 可发现性 (discoverability) 概念的问题: 或者说, 即使你不太清楚要搜索什么, 你如何才能找到你想要的东西.

Emacs 28 添加了 M-S-x, 这是一个将可执行命令限制为与当前缓冲区 (current buffer) 相关的精选集合的命令.

由于它从具有专用按键绑定的命令列表或模式作者手动选择的命令列表中提取, 你可能会发现–因为它是一个最近添加的功能–它相当稀疏. 尽管如此, 我建议你记住它, 并将其作为探索 Emacs 工具箱中的另一个工具来使用.

  1. 处理 Shift

    你可能已经注意到 M-S-x 中的 S-. 另一种写法是 M-X, X 大写, 因为当你按住 shift 时, x 会变成 X. 我觉得前者更容易阅读, 但两者都是有效的表示法.

3.0.7. 通用参数 (Universal Arguments)

有些命令具有备用状态, 要访问它们, 你需要给它们一个通用参数 (universal argument) (也称为前缀参数 (prefix argument)). 通用参数也因其按键绑定 C-u 而闻名. 当你为另一个按键绑定 (顺便说一句, 这也包括 M-x) 添加前缀时, 你是在告诉 Emacs 修改该命令的功能. 接下来会发生什么取决于你调用的命令: 有些命令有零个, 一个甚至更多通用参数状态. 如果一个命令有 N 个状态, 你只需输入 C-u 最多 N 次.

通用参数是数字 4 的简写. 如果你输入 C-u a, Emacs 会在屏幕上打印 aaaa. 如果你输入 C-u C-u a, Emacs 会显示 16 个字符 (因为 4 乘以 4 等于 16). 请记住, 通用参数本身是完全无效的. 当你输入它们时, Emacs 会像前缀键一样等待, 直到你给出一个后续命令–只有到那时 Emacs 才会应用通用参数.

理解 Emacs 的命令状态仅仅是数字是一个很有用的知识, 因为你也可以向命令传递任意数字. 许多 Emacs 黑客会写 C-u 10 a 来打印 10 个字符, 但有一个更简单的方法.

顺便一提 当你按下一个键–比如说键盘上的 a 键–Emacs 是如何在屏幕上写出它的呢? 事实是有一个特殊的命令叫做 self-insert-command, 当它被调用时, 会插入最后输入的键. 这个命令给按键和命令带来了对称性: 它使你的普通键盘字符的行为与 Emacs 中所有其他命令完全相同. 这也意味着键盘字符, 因此也包括 self-insert-command, 都受制于与所有其他命令完全相同的规则. 它们可以被你解除绑定, 重新绑定以及以其他方式修改.

绑定到按键绑定 C-0 到 C-9 的是数字参数 (digit arguments). 但它们不仅仅绑定到那一排按键上, 目的是为了保持我个人称之为打字节奏 (tempo) 的东西–但下面会更多地讨论节奏.

以下是将数字参数传递给命令的各种方法.

Key Binding Notes
C-u 数字参数 4
C-u C-u 数字参数 16
C-u C-u … 数字参数 \(4^n\)
M-0 到 M-9 数字参数 0 到 9
C-0 到 C-9 数字参数 0 到 9
C-M-0 到 C-M-9 数字参数 0 到 9
C-- 负参数
M-- 负参数
C-M-- 负参数

负参数命令绑定到减号键 (-), 尽管从上表中很难看出来. 它们写成 C– 而不是 C- -, 因为后者是一个无效的 Emacs 按键: 你不能按下一个修饰键 C-, 释放它, 然后再按 -. 那样只会在你的屏幕上打印 -. 是减号本身绑定到了几个修饰键上. 空格很重要.

所以我提到了节奏 (tempo) 的重要性. 一旦你熟悉了 Emacs, 你就会在屏幕上飞快地移动, 而不必将手指从修饰键上移开来应用负参数或数字参数, 这将帮助你做到这一点. 确保数字和负参数绑定到修饰键 C-, M- 和 C-M- (三种非常常见的修饰键组合), 几乎可以保证你在用预期的命令跟进之前不必将手指从修饰键上移开.

以下是一些我的意思的例子.

  • M– M-d 会 kill 光标前的上一个词. 如果没有 M–, M-d 会 kill 光标后的词. 该命令与负参数具有协同作用, 因为你可以将手指保持在 meta 键上并按 - d. 这种组合保持了你的节奏.
  • C– M-d 的作用完全相同, 但输入时间大约是原来的三倍. 你必须按 C–, 释放 control 键, 然后按 M- 再按 d. 这种组合打断了你的节奏.

许多人从不费心将数字和负参数融入他们的工作流程中, 但我发现它们非常有益. 像更改我刚输入的单词的大小写这样的事情, 可以通过给定负参数来反转命令的方向轻松完成.

保持你的节奏, 避免将手指从本位行22移开. 负参数为命令添加方向性; 数字添加重复或更改命令的工作方式.

3.0.8. 发现和记住按键

如果你记不清某个命令的确切名称, Emacs 可以提供帮助. 比如说你记不清如何打印段落字符 ¶, 但你确实记得它在 C-x 8 按键映射中的某个位置, 那么你只需要在任何前缀键后附加 C-h, 就能得到属于该按键映射的所有绑定的列表.

输入 C-x 8 C-h 会显示一个计算机生成的按键及其命令列表. 这个界面是超链接的, 是 Emacs 自文档化帮助系统的一部分.

Key Binding
C-x 8 " Prefix Command
C-x 8 < «
C-x 8 > »
C-x 8 ? ¿
C-x 8 C ©
C-x 8 L £
C-x 8 P
C-x 8 R ®
C-x 8 S §
C-x 8 Y ¥

上面是你请求 C-x 8 帮助页面时看到的部分命令. 如果你在 Binding 列中只看到一个字符, 那意味着当你输入该键时它会打印该字符.

然而, Emacs 也会告诉你是否有更多具有更深层级的子前缀键; 在本例中, C-x 8 " 有其他按键绑定到它.

所有这些按键, 隐藏在 Emacs 尘封的深处, 都随意地绑定到键盘字符的各种可能排列组合上, 这可能看起来很奇怪, 特别是如果你来自像 Vim 这样的模式编辑器.

80 年代早期使用的特定键盘的遗留问题在 Super, Hyper 和 Meta 这些名称中显而易见.

那时, 大多数 Emacs 按键都绑定到更大范围的物理键盘修饰键上, แต่当键盘制造商 (以及制造键盘所插入机器的业务) 破产时, Emacs 不得不与时俱进. Emacs 的开发者们没有取消 Emacs 的基石, 而是重新调整了按键, 使它们能在普通的, 无聊的 PC 键盘上工作.

所以你可能在想, 记住所有这些按键确实是一项艰巨的任务–但你不需要. 我只记住我经常使用的 (正如我们人类大脑习惯做的那样), 其余的留给 Emacs 去记.

如果你忘记了某个特定的按键组合, 请使用 Emacs 的帮助系统. 你总是可以在前缀键后附加 C-h.

3.0.9. 配置 Emacs

修改 Emacs 是每个 Emacs 黑客最喜欢的消遣. 去参加 Emacs 聚会或者和有经验的 Emacs 黑客聊天, 谈话总会不可避免地转向他们为了让生活更轻松而做的小改动和技巧.

知道如果你不喜欢编辑器的某个行为方面就可以简单地改变它, 这很有趣 (也很有益)–事实上, 关于改变 Emacs 这个主题完全可以写一整本书.

在本书中, 我会提出一些需要更改的建议. 在可能的情况下, 我会使用 Customize 界面, 而不是通常建议 elisp 片段的方法.

如果你想更改 Emacs, 你有两个选择:

  • 使用 Customize 界面, 因为它是内置的并且设计为用户友好的. 我这么说, 但很多人觉得它 cumbersome (笨重) 且难以使用. 我认为这有点不公平: 它很实用, 并且必须支持许多任意方式来配置相当复杂的功能. 并非所有内容都受 Customize 支持. 由于你需要编写 elisp 来更改变量, 并且由于 LISP 使用的数据即代码范式, 你会发现 Customize 可以编写它被告知如何编写的 elisp, 并且仅限于特定选项. 这使得跨 Emacs 的许多, 许多设置推广一个界面几乎不可能. 但大多数 Emacs 的内置包都支持 Customize 界面, 许多第三方包也支持. 我强烈建议你尽可能使用 Customize 界面, 直到你熟练编写 elisp 为止.
  • 编写 elisp 来更改你想要自定义的内容. 这是最强大的选项, 但也是最复杂的. 你需要学习 elisp (这并不太难, 编写它通常很有趣) 来做到这一点, 但我认为从长远来看, 这是值得的. 当我自己更改字体外观时, 我仍然使用 Customize 界面. Emacs 中有数百种字体外观; 从字体锁定外观 (语法高亮) 到模式行的颜色, 再到用于 info 手册的字体等等.

3.0.10. Customize 界面

Customize 界面分为组 (groups) 和子组 (sub groups). 每个组通常代表一个包, 模式或功能块. 顶级组称为 Emacs, 并且如你所料, 包含所有其他组.

要访问 customize 界面, 请键入 M-x customize. 应该会出现一个名为 Customize Group: Emacs 的缓冲区, 其中包含组列表. 这是 Emacs 中使用鼠标可能有益的一部分; 该界面具有按钮, 超链接和编辑框, 就像浏览器一样. 到处点击–探索界面, 惊叹于有多少东西可以配置! 而这些仅仅是暴露给 Customize 界面的东西.

  1. 在 Customize 中搜索

    你可以使用顶部的搜索栏搜索可自定义的选项.

    font-lock-string-face.png

    Figure 5: Customize 界面示例, 显示 font-lock-string-face 的设置.

    Customize 界面相当复杂, 但一旦你了解了它的工作原理, 就很容易使用了. 上图显示了一个 face: font-lock-string-face. 这是该 face 的实际 elisp 变量名; 美化后的名称是 Font Lock String Face, 这也是你将在上图中看到的. 在其左侧紧挨着的是一个箭头–它很小, 但它会隐藏/显示每个 face. 在终端中, 它被替换为更易读的文本 Hide 或 Show.

    顺便说一句, Customize 界面由两部分组成: faces 和 options. Options 是你可以 Customize 的非 face 内容的总称.

    font-lock-string-face 控制字符串的 face–而字符串是什么取决于它在哪个模式中使用. 对于大多数编程主模式, 它将用于源代码中的实际文字字符串, 但模式作者可以自由地使用它.

    customize.png

    Figure 6: Customize 界面中 font-lock-string-face 的可配置属性.

    我个人前景 face 颜色是 OrangeRed. 但没有什么能阻止我添加其他属性, 正如上图所示.

  2. 支持的颜色

    如果你在 GUI 中使用 Emacs, 你只受显示器颜色深度的限制, 并且可以从 RGB 颜色空间中自由选择任何颜色. 我使用命名颜色, 要查看支持的名称列表, 你可以键入 M-x list-colors-display. 如果你在终端上, 将显示你的终端支持的颜色.

    终端也支持 24 位颜色, 除了通常的 16 或 256 色. 如果你没有看到预期的颜色, 你应该阅读 Info 手册 FAQ 中关于如何配置终端的部分:

    键入 M-x info-apropos 然后输入 Colors on a TTY. 一小会儿后, 你应该会看到一个指向 FAQ 的超链接, 其中解释了如何配置你的终端设置.

    在 Emacs 28 中, 你可以设置环境变量 COLORTERM=truecolor, Emacs 会自行判断需要做什么, 而不考虑你系统的终端功能. 如果你难以获得 24 位颜色, 请尝试设置该环境变量.

    在 Customize UI 中进行更改是不够的. 你还必须应用更改并选择性地保存它们. 如果你不保存它们, 更改将不会在 Emacs 会话之间持久存在. 按下恰当命名的 Apply 和 Apply and Save 按钮即可完成此操作. Revert… 按钮类似, 但有更多选项. 如果你对已应用的更改不满意, 你只需要 Revert This Session' s Customizations. 请记住, 它只会还原你在当前缓冲区中所做的选项–而不是全局所做的所有自定义.

    永远记住, 在保存之前, 你可以还原你的更改. 之后, 你必须手动逐个撤销或使用 Revert… 按钮的 Erase Customizations 选项.

    所有自定义默认存储在你的 init file 中 (或者可能是一个单独的 custom file), 并且像 Emacs 的其他部分一样, 更改以 elisp 代码的形式存储, 这使得你可以返回并手动更改 elisp.

    除了通过组树导航之外, 你还可以使用几个快捷命令之一:

    • M-x customize 显示 Customize 界面和所有组.
    • M-x customize-option 要求提供一个可自定义的选项, 并打开包含该选项的 Customize UI.
    • M-x customize-browse 打开一个树形组浏览器. 很像常规的 Customize 界面, 但没有组描述.
    • M-x customize-customized 自定义你已更改但未保存的选项和 faces. 如果你记不清是否有未保存的选项, 这值得记住.
    • M-x customize-changed 显示自特定 Emacs 版本以来更改的所有选项. 这是发现新功能和选项的好方法.
    • M-x customize-face 提示输入要 Customize 的 face 名称. 我建议你将光标放在要更改的 face 上. 它会自动填入名称.
    • M-x customize-group 提示输入要 Customize 的组名 (例如, python).
    • M-x customize-mode 自定义当前缓冲区的主模式. 你应该为你使用的每个主模式执行此操作. 这是更改内容并大致了解主模式功能的快捷方法.
    • M-x customize-saved 显示所有已保存的选项和 faces. 如果你想查找并禁用错误的更改, 这非常方便.
    • M-x customize-themes 显示已安装主题的列表, 你可以切换到这些主题.

    我鼓励你使用 Customize 界面来配置 Emacs. 它只有一部分你可以 (或想要) 更改的内容, 但这足以让你开始个性化 Emacs 之旅.

    随着你继续使用和个性化 Emacs, 你最终可能会达到 init file 难以管理的程度. 当发生这种情况时, 通常会将更改分成相关更改的组. 然而, 在你熟悉 (并且你的 init file 已经不堪重负) Emacs 之前, 这是一项低优先级任务.

3.0.11. 求值 Elisp 代码

你经常会在互联网上找到或编写 elisp 代码片段, 并且你会想要对它们求值–每次都关闭并重新启动 Emacs 太麻烦了.

有很多不同的方法可以做到这一点, 我只展示了其中几种可用的方法. 你可以阅读<在 Emacs 中求值 Elisp>(Evaluating Elisp in Emacs)23 来深入学习这个主题.

3.0.12. 包管理器 (The Package Manager)

Emacs 自带一个包管理器, 可以无缝显示和安装来自集中式仓库的包.

有几个可用的包仓库, 你的 Emacs 配置为从官方的 GNU 仓库读取. 但是, 你也应该添加 MELPA, 这是一个由志愿者运行的包仓库. 包管理器会将所有不同的列表合并为一个.

由于这些仓库由志愿者私下拥有, 它们可能会暂时或永久关闭–所以我建议你查看 Emacs Wiki24 以获取最新的仓库列表.

3.0.13. 颜色主题 (Color Themes)

如果你不喜欢 Emacs 中的默认配色方案–那么好消息是, 你可以使用颜色主题. 键入 M-x customize-themes 查看已安装颜色主题的列表. Emacs 的包管理器或像 Github 这样的网站上还有更多免费可用的主题.

要使用包管理器安装主题, 请打开包管理器 (M-x package-list-packages) 并查找主题; 大多数主题的后缀为 -theme, 它们的行为和安装方式与普通包一样. 安装完所需的主题后, 使用 M-x customize-themes 界面来试用它们. 你可以使用<Customize 界面>中描述的常规 Customize 界面来覆盖你不喜欢的特定颜色. 在 Customize 界面中所做的更改优先于主题.

我应该提一下, 你可以同时激活多个主题, 所以请确保你意识到这一点.

3.0.14. 获取帮助

正如我之前在谈论按键时提到的, Emacs 是一个复杂的自文档化编辑器. Emacs 的方方面面都是可搜索或可描述的. 学会如何做到这一点对于精通 Emacs 来说绝对至关重要. 知道如何找到问题答案的效用怎么强调都不为过. 我一直使用 Emacs 的自文档化功能; 用来唤醒我的记忆, 或者寻找我不知道的问题的答案.

  1. 术语

    当我讨论 Emacs 的文档系统时, 你会经常看到 symbol 这个词. 这个词并非 Emacs 的那一部分所独有, 而是表达了你可以合理查找的任何东西: 无论是一个变量, 函数, face 还是其他完全不同的东西.

    我还没有谈到 Emacs 的实际核心 (移动, 编辑等等), 因为尽管这对于精通 Emacs 来说显然至关重要, 但它们是特定的技能, 你可以通过耐心使用 Emacs 的自文档化帮助系统来获得.

    知道如何获取帮助至关重要, 因为:

    • Emacs 最了解: 你的 Emacs 配置会与其他人–有时只是略有不同, 有时则大相径庭–的 Emacs 配置不同. 在互联网上提问只会得到一般性的答案. 如果你重新绑定了按键, 只有你的 Emacs 知道这些按键是什么.
    • 你会发现更多 Emacs 的功能: 我通过探索偶然发现了无数酷炫的功能–也许是隐藏在某个主模式中的省时命令, 或者是一个改变我常用命令行为的变量. 许多第三方软件包可能没有足够的用户手册, 迫使你阅读源代码或研究该软件包公开的命令和变量.
    • 它会帮助你解决问题: 我一直帮助人们解决 Emacs 问题, 但我并非无所不知–我知道的是去哪里查找以及如何阅读文档.
    • 它给你信心: 不知道如何在 Emacs 中做某事是正常的, 但也很令人困惑. 但是能够说" 哦, 我不知道如何做这个, 但我知道去哪里寻求帮助" –你对 Emacs 的信心会随着你的知识一起增长.

    Emacs 的帮助系统大致分为三个部分, 了解你需要哪个部分以及何时需要, 可以节省你的时间.

3.0.15. Info 手册

Emacs 自己的手册 (实际上, GNU 生态系统中的所有手册) 都是用 TeXinfo 编写的. 如果你曾经使用过命令行工具 info, 那么你就与 TeXinfo 超文本查看器进行过交互. Emacs, 显而易见, 有自己的 info 查看器. Emacs 的 info 手册包含的不仅仅是与 Emacs 相关的主题. 默认情况下, info 浏览器会索引你系统上安装的所有其他 info 手册–诸如 GNU coreutils 手册之类的东西也会出现.

很多人不喜欢 info, 我不确定为什么. 它的工作方式与 Web 浏览器非常相似, 尽管按键绑定确实不同.

要访问 Emacs 的 info 阅读器, 请键入 M-x info 或按 C-h i. info (文档浏览器) 将出现, 你可以自由使用鼠标单击超链接, 或使用此键盘快捷键表进行导航:

Key Purpose
[ 和 ] 上一个/下一个节点
l 和 r 在历史记录中后退/前进
n 和 p 上一个/下一个同级节点
u 上升一级到父节点
SPC 一次滚动一屏
TAB 在交叉引用和链接之间循环
RET 打开活动链接
m 提示输入菜单项名称并打开它
q 关闭 info 浏览器

因为 info 手册具有层级结构, 与本书和大多数其他书籍的方式非常相似, 如果你正在从头到尾阅读 info 手册, 你会想用 [ 和 ] 来导航. 这相当于从一个章节开始阅读一本书, 依次浏览所有子章节, 子子章节等等, 按照它们排列的顺序.

  1. 日常阅读

    对于日常阅读, 你需要用 SPC 来浏览和阅读, 因为它" 随心所欲" . 它会翻阅页面直到到达末尾. 然后, 它要么选择下一个子节点, 要么选择下一章. 要浏览, 请使用 [ 和 ] 在节点之间来回切换.

    相反, 如果你想跳转到下一个或上一个同级节点, 你应该使用 n 和 p. 要在历史记录中后退或前进 (很像浏览器), 请使用 l 和 r.

    按键 u 会上升一级到父节点; TAB 会在超链接之间循环, RET 会打开它们.

    大多数 info 手册也以 HTML 版本在线发布, 那么为什么还要使用 Emacs 自己的阅读器呢? 首先, 你可以使用 Emacs 的通用书签系统 (稍后会详细介绍). 你几乎可以在 Emacs 中为所有内容添加书签: info 页面, 文件, 目录等等. 另一个优点是它在 Emacs 中, 所以如果你正在阅读 Emacs 出色的<Emacs Lisp 编程入门>(An Introduction to Programming in Emacs Lisp) 并同时编写代码, 将 info 手册放在旁边的拆分窗口中会特别方便.

    如果你想阅读某个特定的 Emacs 功能, 你必须先打开 Emacs 手册. 为此, 请键入 C-h i 后跟 m. 当提示输入菜单项时, 输入 Emacs 表示 Emacs 手册, 或 Emacs Lisp Intro 表示 elisp 简介. 与往常一样, 支持 TAB 补全. 你也可以浏览手册的主列表并找到你想阅读的那一个.

    Emacs 28 你可以使用按键绑定 C-h R 打开一个手册. 要打开 Emacs Lisp Intro, 请键入 C-h R eintr.

    你可以通过键入 C-h F 来查找命令的文档, 并在提示符下输入命令的名称. Emacs 会跳转到 info 手册中描述该命令的正确位置.

3.0.16. Apropos

Emacs 有一个广泛的 apropos 系统, 其工作方式与命令行上的 apropos 非常相似. 如果你不太确定要查找什么, apropos 系统尤其适用. 有各种各样的小众命令, 它们只搜索 Emacs 自文档化内部机制的特定方面.

Apropos 是你工具箱中的另一个工具. 它的亮点在于你可以将查找范围缩小到特定区域. 如果你正在查找变量, 可以使用搜索变量的 apropos 系统; 如果你正在查找命令, 可以按命令搜索. 并且所有 apropos 都支持正则表达式.

最常用的一个, 绑定到 C-h a, 是 M-x apropos-command. apropos-command 显示所有与给定模式匹配的命令 (并且仅显示命令, 不显示函数).

例如, 你可能正在寻找处理单词的命令 (但关于" 单词" 实际含义的更多信息, 请参阅" 什么构成单词?" ), 因此输入 C-h a 后跟 -word$ 是一个不错的起点. 这将列出所有以 -word 结尾的命令.

以下是你运行该命令时会看到的部分输出:

Command Key Purpose
ispell-word M-$ 检查光标下或光标前单词的拼写.
kill-word M-d 向前删除字符, 直到遇到单词末尾.
left-word C-<left> 将光标向左移动 N 个单词 (如果 N 为负数则向右).
mark-word M-@ 将标记设置在距离光标 ARG 个单词远的位置.

如你所见, 你会得到命令的名称, 与之绑定的按键 (如果有) 以及其用途. Emacs 有一些命名约定, 一旦你熟悉了 Emacs, 你就会看到某些模式出现. 例如, 通常会在命令后加上它操作的语法单位或上下文作为后缀: -word 表示单词, -window 表示窗口, 等等.

提示 Apropos 可以按相关性对结果进行排序. 键入 M-x customize-option apropos-sort-by-scores 来进行自定义.

有各种各样的 apropos 命令可以用来查询 Emacs. apropos-command 是我选择的最重要的记忆命令. 因为它可以让你按模式搜索, 你只需要记住命令名称的一部分, 而不是全部, 就能找到你要找的东西. 这也是偶然发现 Emacs 新功能的好方法. 给 apropos-command 输入 .+ 模式 (匹配所有内容) 会产生 Emacs 知道的数千个命令.

Emacs 有一系列专业的 apropos 命令, 你可能会发现它们更合适.

  • M-x apropos: 终极选项. 此命令将显示与给定模式匹配的所有符号. 如果你试图查找与某个模式相关的变量, 命令和函数.
  • M-x apropos-command 或 C-h a: 正如我上面解释的, 此命令将仅列出命令.
  • M-x apropos-documentation 或 C-h d: 仅搜索文档. 在 Emacs 的行话中, 这意味着你可以为符号提供的文档字符串 (documentation string). 如果所有其他选项都失败了, 它有其用途.
  • M-x apropos-library: 列出库中定义的所有变量和函数. 如果你正在研究新的模式或包, 请使用它, 因为它列出了该库中定义的所有函数和变量.
  • M-x apropos-user-option: 显示通过 Customize 界面可用的用户选项. 这是获取 Customize 选项符号名称的一种方法, 但如果你想搜索 Customize 界面, 最好使用 Customize 界面中的搜索框, 因为它也允许你自定义匹配项. 我从不使用它.
  • M-x apropos-value: 搜索具有特定值的所有符号. 如果你正在寻找一个包含特定值的变量, 这个命令可能对你有用. 一个潜在的用途是, 如果我知道一个变量的值, 但不知道它的名称或定义位置.

如果你不确定要查找什么–也许你只有一个名称的一部分, 或者你只记得文档的一小部分–那么 apropos 是一个可以帮助你的工具. 我发现 apropos 不可或缺; 它是列出与特定模式匹配的所有命令的好方法, 也是发现新命令的更好方法.

3.0.17. 描述系统 (Describe System)

Emacs 自文档化特性的精髓在于其命令的 describe system. 如果你知道要查找什么, describe 就会解释它是什么. Emacs 的方方面面–无论是用 elisp 编写的代码还是用 C 编写的核心层–都可以通过 describe system 访问和索引. 从按键到命令, 字符集, 编码系统, 字体, faces, 模式, 语法表等等–一切都在那里, 分类清晰.

describe system 不是静态的. 每次你查询 Emacs 的特定部分时, 它都会通过一个内部自省层获取所需的详细信息, 该层本身会查询 Emacs 自己的内部数据结构. 你可以通过 elisp 查询自省层和内部数据结构. Emacs 中没有" 秘密" –当然, 文档化的 API 层是访问 Emacs 自身内部状态的推荐方式, 但与其他编辑器和 IDE 不同, 你不受包作者或 Emacs 维护者的束缚. 我认为这种开放性的体现, 被 describe system 完美地捕捉到了, 是 Emacs 最好的特性之一.

你可以找到绑定到 C-h 前缀键25的最重要的 describe 键; 实际上还有更多, 但我认为其中大部分对除了 elisp 编写者之外的所有人都用处有限.

  1. Help 缓冲区中的便捷快捷键

    你会在缓冲区中找到指向源代码定义和 customize 界面 (如果存在该选项) 的超链接. 但 Emacs 28 为所有 describe 缓冲区添加了几个方便的快捷键.

    Key Binding Purpose
    i 打开 Info 手册
    s 跳转到源代码定义
    c 打开 Customize 界面

4. 第四章 移动理论

Escape Meta Alt Control Shift – info.gnu.emacs

四处走动, 以及高效地四处走动, 与知道如何快速高效地编辑文本同样重要. 但在 Emacs 中, 移动不仅仅是缓冲区中的字符; 还有许多辅助技能构成了导航, 例如理解 Emacs 相当复杂的窗口系统.

我不会期望你马上记住并应用这里学到的所有东西. 我已经把内容安排好了, 你可以从头开始, 边读边逐步学习, 拾遗补缺. 最重要的部分, 正如我多次强调的那样, 是给它时间和练习–在你的日常生活中花点时间问问自己, 是否有更好的方法来解决你面临的问题.

Emacs 中的移动可以是局部的, 区域性的或全局性的. 局部移动是指你在点附近编辑和移动文本时所做的事情. 语法单元 (syntactic unit)–一个半正式的术语, 指的是作用于一组字符的命令–可以是字符, 单词, 行, 句子, 段落, 平衡表达式等等. 区域移动和局部移动类似, 但区域移动涉及整个函数或类定义 (如果你在编写代码), 或者章节之类的结构 (如果你在写散文). 全局移动是指从一个缓冲区移动到另一个缓冲区, 或者从一个窗口移动到下一个窗口.

初学者首先看到的是 Emacs 创建窗口的嗜好: 当你查看帮助文件时, 当你编译文件时, 或者当你打开 shell 时. 如果你从未使用过平铺窗口管理器 (tiling window manager) (因为 Emacs 正是如此), 那么拆分和删除窗口的想法可能看起来很奇怪–在其他编辑器中, 你可能会使用拆分窗格, 但你几乎从不改变它以适应手头的任务.

在 Emacs 中, 窗口是瞬态的; 它们会根据你的需要出现和消失. 你可以保存你的窗口配置 (并且有几种方法可以做到这一点), 但它们从来都不是一成不变的, 不像许多编辑器那样–一次设置, 再也不变. 你必须习惯这一点. 现在, 有许多变量可以用来微调 Emacs 的窗口行为, 但你无法真正通过调整来摆脱使用 Emacs 的窗口. 一些软件包试图用框架替换窗口, 取得了一些成功, 但它们本质上是权宜之计, 我建议你至少在熟悉 Emacs 的系统之前避免使用它们.

缓冲区在不再需要时很少被 kill (即关闭); 大多数 Emacs 黑客只会切换到其他东西, 只在需要时才返回. 这可能看起来很浪费, 但每个缓冲区 (除了各种元数据和缓冲区的特定编码系统) 只比其中的字符字节大小略大一点. 一个典型的 Emacs 会话在重新启动之间会持续数周, 大多数 Emacs 黑客同时运行数百个缓冲区而没有任何问题.

无论你在 Emacs 中做什么任务, 你都需要处理缓冲区和窗口的概念以及如何处理它们. 谢天谢地, 这可以很简单, 也可以很复杂, 具体取决于你的期望或你希望如何设置.

4.0.1. 基础知识

顺便一提 你重新映射 Caps Lock 了吗? 阅读" Caps Lock 用作 Control 键" 来理解为什么这如此重要.

学习查找和保存文件, 更改缓冲区以及日常使用的基本按键绑定是掌握 Emacs 之路的第一步. 然而, 在你记住这些按键之前, 你可以自由使用菜单栏来完成这些操作. 关于菜单栏需要注意的一点是, 它在终端中是不可点击的. 相反, 你必须按 F10 来激活并通过键盘导航菜单栏.

正如我在" 获取帮助" 中解释的那样, 如果你没有看到菜单栏 (它应该同时出现在 GUI 和终端 Emacs 中), 并且你已经更改了 Emacs 的配置–例如, 使用了入门套件或同事的 init 文件–你可以通过键入 M-x menu-bar-mode 来显示它, 但你仍然需要找到配置中隐藏它的部分.

一旦你成为传奇的 Emacs 黑客, 你自然会想要隐藏它, 因为它占用了宝贵的屏幕空间. 在那之前, 请保持启用状态. 我鼓励你保持启用状态, 直到你对自己的 Emacs 技能有足够的信心, 不再需要它为止.

大多数主模式也有自己的菜单栏条目, 从而提高了主模式的可发现性. 我刚开始使用时很长一段时间都使用菜单栏, 它真的帮助了我, 因为我可以专注于记住像导航和编辑这样的重要命令.

如果你更喜欢纯键盘操作, 有一些你必须知道的按键绑定才能在 Emacs 中执行基本任务.

Key Binding Purpose
C-x C-f 查找 (打开) 文件
C-x C-s 保存缓冲区
C-x b 切换缓冲区
C-x k Kill (关闭) 缓冲区
C-x C-b 显示所有打开的缓冲区
C-x C-c 退出 Emacs
ESC ESC ESC 退出提示, 区域, 前缀参数并返回到单个窗口
C-/ 撤销更改
F10 激活菜单栏 27
  1. C-x C-f: Find file

    在 Emacs 中打开文件称为查找文件 (finding a file) 甚至访问文件 (visiting a file). 话虽如此, 说打开 (open) 也是完全可以的. 原因在于 Emacs 实际上并不区分打开现有文件和创建新文件. 我交替使用这些术语.

    所以, 如果你输入 C-x C-f 并输入 /tmp/hello-world.txt, Emacs 会访问它, 无论它是否存在; 如果不存在, 则会显示一个空缓冲区.

    1. 主模式加载顺序

      当你访问一个文件时, Emacs 会选择一个主模式. 大多数编辑器对文件扩展名有很多你无法轻易更改的假设. Emacs 支持一系列检测机制, 所有这些机制都可以根据你的需要进行更改. 它们按应用顺序列在此处.

      • 文件局部变量 (File-local variables) 是 Emacs 可以根据文件中的内容为每个文件启用的变量. 它们可以作为头部出现:

        -*- mode: mode-name-here; my-variable: value -*-
        

        或作为尾部出现:

        Local Variables:
        mode: mode-name-here
        my-variable: value
        End:
        

        Emacs 也会查看使用该主模式注释语法的注释行. 值得注意的是, 读入 Emacs 的文件变量是该文件缓冲区的局部变量 (意味着其他缓冲区不受影响). 这意味着如果你有仅适用于该文件的特定设置, 你可以将它们添加到头部或尾部, Emacs 会自动加载它们. 在实践中, 这意味着从缩进设置到更复杂的变量都可以通过文件变量来控制.

      由于 Emacs 实际上是直接从文件中运行代码, 因此所有 Emacs 变量都分为安全 (safe) 和不安全 (unsafe) 的文件变量: 被声明为安全的变量–通常由 Emacs 维护者声明–会自动求值. 对于不安全的变量, 你必须首先告诉 Emacs 该怎么做: 你可以忽略该变量; 或者仅对该文件临时求值一次; 或者将其声明为安全.

      • 程序加载器指令 (Program loader directives) 或 shebangs 也受支持. 如果你的文件以 #! 开头–例如 #!/usr/bin/env python 或 #!/bin/bash–那么 Emacs 会找出主模式并在 Emacs 中可用时运行它. 变量 interpreter-mode-alist 列出了 Emacs 可以检测到的程序加载器.
      • 魔术模式检测 (Magic mode detection) 使用 magic-mode-alist 变量来查看文件开头是否与魔术模式变量中存储的模式匹配. 如果你无法注释文件或提前预测文件名或扩展名, 则此检测模式适用.
      • 自动模式检测 (Automatic mode detection) 是大多数主模式的应用方式. Emacs 有一个非常庞大的模式注册表, 用于匹配文件扩展名, 文件名或文件路径的全部或部分, 存储在变量 auto-mode-alist 中. 例如, 如果你打开 /etc/passwd, Emacs 会检测到这一点并使用 etc-passwd-generic-mode 主模式打开文件. 如果文件名以 .zip 结尾, Emacs 则会以 archive-mode 打开文件.

      尽管不同的启发式方法可能看起来很复杂, 但好消息是工作已经为你完成了. Emacs 的主模式检测相当复杂, 几乎总能为你选择正确的东西.

    2. 编码系统和行尾符

      Emacs 应用了另外两个你应该了解的重要启发式方法: 编码系统 (coding systems) 和行尾符 (line endings).

      • 编码系统: Emacs 具有出色的 Unicode 支持 (键入 C-h h 查看演示), 包括在不同编码系统之间透明地读写, 双向从右到左脚本支持, 键盘输入法切换等等. 要查看当前缓冲区使用的编码系统, 你可以键入 C-h C <RET>. Emacs 会显示大量信息, 包括与该缓冲区关联的所有编码系统–但对于文件, 它们几乎总是设置为相同的编码系统. 模式行也会给你一个大概的提示:

        U:**- helloworld.c 92% of 5k ...
        

        第一个字符 U 表示缓冲区 helloworld.c 使用多字节编码系统. 如果它显示 1, 则通常表示 ISO 字符编码的第 1 部分. 确切的助记符将取决于你使用的数百种受支持编码系统中的哪一种–因此 C-h C <RET> 是查看其确切含义的可靠方法.

      • 行尾符: 当你打开一个文件时, Emacs 会确定使用的行尾符. 如果文件使用 DOS 行尾符, 那么当你打开文件和保存文件时它们都会被保留. UNIX 和 OS X 之前的 Macintosh 编码也是如此. 模式行会告诉你正在使用哪种行尾符:

        U:**- helloworld.c 92% of 5k ...
        

        如上所述, 第一个字符 U 表示编码系统. : 表示它是 UNIX 风格的行尾符. 对于 DOS, 它会显示 (DOS), 对于 Macintoshes, 则显示 (Mac).

  2. C-x C-s: Save Buffer

    在" 缓冲区" 中, 我解释了 Emacs 中的缓冲区不必是文件系统上的文件, 而可以是一个临时缓冲区, 用于网络 I/O 之类的事情, 或者只是一个用于处理文本的草稿文件. 因此, 这在实践中意味着你可以在 Emacs 中保存任何缓冲区–即使是像帮助缓冲区或网络 I/O 缓冲区这样的内部缓冲区.

    当你要求 Emacs 保存缓冲区时, 它会将其保存到与该缓冲区关联的文件中–当且仅当该缓冲区具有关联的文件名时–或者如果不存在文件名, 则会要求你输入一个名称. 后一种情况通常发生在你正在保存一个尚未分配文件的缓冲区时; 也许它是一个临时缓冲区, 甚至是帮助命令的输出.

    • 将缓冲区写入文件: 如果你想将缓冲区保存到不同的文件–类似于其他编辑器中的" 另存为…" –你可以使用命令 C-x C-w 将其写入新文件.
    • 保存所有文件: 你可以键入 C-x s, Emacs 会依次询问你是否保存每个未保存的文件.
  3. C-x C-c: Exits Emacs

    你可以退出 Emacs–或者只是终止你的连接, 如果你正在客户端-服务器模式下使用 Emacs–但 Emacs 只会在询问你是否要保存未保存的文件后才会退出.

    当 Emacs 要求你保存文件时, 你有几个选项:

    Key Binding Purpose
    Y 或 yes 保存文件
    N 或 DEL 跳过当前缓冲区
    q 或 RET 中止保存, 继续退出
    C-g 中止保存并退出
    ! 保存所有剩余缓冲区
    d 比较文件系统上的文件与缓冲区中的文件

    上面的大多数命令都是不言自明的. Emacs 会遍历所有未保存的文件列表, 但不是所有未保存的缓冲区. 正如你之前可能记得的那样, 有些缓冲区可能没有附加到任何一个文件.

    如果你尝试在不保存的情况下退出, Emacs 总会最后再问你一次是否要继续.

  4. C-x b: Switch Buffer

    如果你一次编辑多个文件–或者在文档缓冲区或特定于模式的缓冲区 (例如 Python 的 shell) 之间切换–知道如何快速高效地切换缓冲区非常重要.

    与大多数窗口管理器中的 Alt+TAB 类似, Emacs 会记住你上次访问的缓冲区, 因此当你键入 C-x b 时, 前一个缓冲区的名称是默认操作–这意味着按 RET 会将你带到该缓冲区.

    切换缓冲区是 Emacs 黑客的第二天性. 一旦你习惯了它, 你甚至都不会去想它; 你会快速而即时地切换缓冲区, 甚至不会多想一下.

    1. 缓冲区命名约定

      Emacs 中的某些缓冲区与外部程序交互–也许是像 bash 这样的 shell–或者它们保存由 Emacs 本身生成的临时信息. 为了将它们与用户创建的缓冲区区分开来, 它们的名称中带有 * 字符, 例如: buffername.

      文件和缓冲区是两个不同 (但相关) 的概念, 当你考虑到草稿缓冲区 (scratch buffers) 的性质时, 这就说得通了–你创建和使用但无意永久保存的缓冲区. 例如, 如果你想运行一个键盘宏或对一段代码区域进行大量文本编辑, 一个 Emacs 黑客会将其复制到一个临时的草稿缓冲区 (只需切换到一个不存在的缓冲区名称即可创建), 进行必要的编辑, 然后切换回原始缓冲区.

      • 将缓冲区写入文件: 如果你稍后决定要将缓冲区保存到文件系统, 你可以按 C-x C-s 来保存它.
      • 列出缓冲区: 另一个值得学习的命令是 C-x C-b. 它会显示 Emacs 中运行的所有缓冲区的列表. 不过, 我建议你使用它的更强大的版本, M-x ibuffer. 它默认没有绑定,所以我建议你通过将以下内容添加到你的 init file 中来绑定它:

        (global-set-key [remap list-buffers] 'ibuffer)
        
    2. 缓冲区切换替代方案

      内置的缓冲区切换界面相当简陋; 它提供基本的 TAB 补全和一些模糊匹配, 但仅此而已. 事实上, 补全工具对你的用户体验如此重要, 我建议你花时间研究适合你工作流程和个性的补全框架. 如果你正在使用入门套件, 你可能已经有一个预配置并在你的 Emacs 中启用了.

      尽管如此, 对于大多数刚开始的人, 我推荐使用 IDO (如果你的 Emacsen 版本低于 27), 以及 FIDO (一个针对版本 27 或更高版本的直接替代品).

      无论版本如何, 我两者都使用, 并且离不开它们–事实上, 大多数 Emacs 用户肯定在某个时候使用过 IDO 或类似的东西.

      • 对于 IDO 模式: 键入 M-x ido-mode, 然后再试一次 C-x b 或 C-x C-f. 你可以通过自定义选项 ido-mode 来永久启用它: M-x customize-option RET ido-mode RET 你还可以通过启用 flex matching 来改进 IDO 的模糊匹配: M-x customize-option RET ido-enable-flex-matching RET 并且你可以通过运行 M-x customize-group ido 来自定义更多功能. 有关此主题的更多信息, 我建议你阅读<IDO 模式简介>(Introduction to IDO mode)28.
      • 对于 FIDO 模式 (Emacs 27 或更高版本): 键入 M-x fido-mode. 此模式有一些相关的补全选项, 但它们属于 umbrella group icomplete, 这是 FIDO 内置的父补全机制. 要查看它们, 请键入 M-x customize-group icomplete.
    3. 其他补全框架

      如今的包管理器中有数十种补全框架. 大多数是相互权衡的, 用一种便利或好处换取另一种: 无论是速度, 简单性, 功能以及与主模式或外部源的集成, 搜索和补全方法, 还是结果的呈现方式. 除了 FIDO 和 IDO 之外, 一些更常见的包括: Helm, ivy, Selectrum, Icicles, Icomplete. 你如何以及以何种方式补全或搜索信息是 Emacs 社区中一个持续不断的争论, 也是探索不同组合和访问信息及工作流程方式的好方法. 我建议你尝试大多数这些框架, 看看它们能做什么: 它们是真正的力量倍增器; 特别是那些能合并来自第三方来源 (命令行工具, 语言服务器等) 结果的框架.

  5. C-x k: Kill Buffer

    在 Emacs 中 kill 一个缓冲区意味着关闭它. 你不必 kill 不用的缓冲区. 让它们在后台运行直到你需要它们为止是完全正常的. 通常, 资深的 Emacs 用户会同时打开数百甚至数千个缓冲区.

  6. ESC ESC ESC: Keyboard Escape

    " 敲三下鞋跟" 键. 如果你卡在某个地方或者想" 恢复正常" –那么按 ESC ESC ESC (很可能) 会解决你的问题.

    所有窗口都会被删除 (意味着它们被隐藏起来), 提示会退出, 特殊缓冲区会隐藏, 前缀参数会取消, 递归编辑级别会解除.

  7. C-/: Undo

    撤销是一项常见的操作, 它绑定到几个按键上: C-/, C-_, C-x u, Edit -> Undo, 甚至如果你的键盘上有物理 undo 按钮, 也可以使用它.

    你更喜欢哪个命令取决于你: 我认为 C-/ 最容易输入, 但如果你的字符集不是美国或英国标准, 你可能更喜欢另一个. 大多数初学者指南会建议你使用 C-x u 甚至 C-_, 但我发现它们比 C-/ 更难输入.

    1. Emacs 28 中的重复按键

      Emacs 28 增加了重复按键 (repeating keys) 的概念. 如果你通过 M-x customize-option RET repeat-mode RET 启用了 repeat-mode, 你可以用 C-x u … u 来链接重复的撤销语句. 并非 Emacs 中的所有按键都这样工作, 但你可以通过 M-x describe-repeat-maps 查看哪些按键可以.

      与其他编辑器不同, Emacs 中的撤销系统可能会引起混淆. Emacs 不是将你所做的更改存储在一个可以撤销和重做的列表中, 而是将其存储在一个称为撤销环 (undo ring) 的东西中.

      大多数编辑器都有一个线性的撤销列表: 你可以撤销和重做, 但如果你撤销然后更改文本, 你将丢失已撤销的步骤; 它们将无法恢复并永远丢失.

      在 Emacs 中, 情况并非如此. 你采取的每一个操作都会记录在撤销环中, 这也包括撤销操作本身. Emacs 会将某些命令组合成一个内聚的撤销单元 (undo cell)–比如连续输入字符或重复多次相同的命令. 某些事件总是会" 封存" 撤销记录并开始一个新的记录. 按 RET, 退格键或移动光标是三个这样的例子.

      重复的撤销命令会撤销越来越多的东西, 但如果你打破了这个循环–例如通过四处移动或编辑文本–Emacs 不会从你离开的地方继续. 相反, 你刚刚撤销的项目会作为重做记录添加到撤销环中. 这意味着当你再次撤销时, 你实际上会撤销 (重做) 你刚刚执行的操作, 直到你回到上次停止时的状态–然后 Emacs 会撤销缓冲区中其余的更改.

      这意味着几乎不可能丢失撤销历史, 因为撤销操作本身就是一个可撤销的操作. 这意味着你可以撤销一些事情–比如在缓冲区中重写一个段落–结果后来才意识到, 实际上, 你更喜欢旧的文本. 在其他编辑器中, 你未完成的更改将永远消失, 因为它们有线性的撤销列表. 在 Emacs 中, 你可以撤销你新写的段落, 直到 Emacs 将你的缓冲区恢复到你上次撤销之前的状态.

      这可能有点令人困惑. 但只要稍加练习, 你很快就能掌握它. 由于使用撤销环几乎不可能真正丢失任何数据, 所以更容易进行实验. 请记住, 只要你连续执行撤销命令, Emacs 就会一直从环中撤销内容. 只有打破这个" 撤销循环" , 你才能重做已撤销的更改. 而打破循环最简单的方法就是简单地移动光标或写入一些内容.

      这是一个简单的例子. 这并不是 Emacs 的撤销环实际保留撤销信息的方式–它要复杂得多–但我认为那个级别的细节是不必要的.

      1. 你坐下来写下以下内容, 其中 █ 代表光标的位置:

        Hello
        Emacs█
        
      2. 现在你用 C-/ 撤销一次:

        Hello
        █
        

        首先要注意的是换行符被保留了; 通常, 换行符会导致一个" 中断" 并在撤销环中创建一个新的撤销单元. 这样做是出于简单的实用主义: 通常, 如果你想撤销某些内容, 你不希望撤销许多行的散文或代码, 而只希望撤销最近的文本. 这与其他程序中的编辑形成对比, 在其他程序中, 你经常会撤销大段文本, 无论你是否愿意.

      3. 再次调用 undo, 你只撤销了换行符:

        Hello█
        
      4. 再次按回车键并输入 World:

        Hello
        World█
        

        此时我们已经写了一些东西; 撤销了其中的一部分; 现在又写了新的文本.

      5. 输入 C-/ 两次会将 World 恢复为 Emacs.
      6. 导航到第一行并将 Hello 修改为 GNU:

        GNU█
        Emacs
        
      7. 现在重复输入 C-/, 你会撤销之前的书写内容, 并显示出在之前的撤销周期中被覆盖的内容. 重复足够多次, 你最终会得到一个空缓冲区. 换句话说, Emacs 在你撤销时不会丢失信息.

      如果在任何时候你中途停止了撤销过程–也许你已经撤销了足够的内容, 使你的缓冲区文本达到了期望的状态–你可以通过发出任何不是另一个撤销命令的命令来打破这个链条. 比如移动.

      打破链条会使撤销环停留在你打破链条时的位置. 后续的撤销命令现在会从那个点开始撤销, 但方向相反: 通过重做之前的撤销步骤.

      还是困惑吗? 这很正常. 这是一个难以" 理解" (并且更难解释) 的概念, 但大多数 Emacs 初学者会随着时间的推移逐渐掌握它. 最重要的一点是, 几乎不可能破坏撤销环并丢失信息–所以尽管去实验吧. 很可能, 一开始你只关心撤销最近的更改.

    2. 模拟经典的撤销-重做行为

      在 Emacs 28 中, 有一个新的命令 M-x undo-redo, 绑定到 C-? 和 C-M-_. 你可以在连续使用常规 undo 命令一次或多次后调用它. 它是 undo 的 redo 命令. 然而, 当你调用它时, 它不会将" redo" 步骤推送到撤销环中. 将其与晦涩的 M-x undo-only 命令–它不会重做之前的撤销–结合起来, 你就拥有了你可能从其他编辑器中熟悉的撤销-重做动态的翻版.

      我建议你花时间熟悉默认的撤销命令, 因为你将不得不在 Emacs 的许多其他部分处理环 (rings) 的概念. 尽管这两个专门的命令可能会在你的工作流程中找到一席之地, 但它们对于简化一个已经难以理解的概念几乎没有帮助.

      顺便一提 你可以为 Emacs 下载其他的撤销实现. 一个流行的是 Undo Tree.29

4.0.2. 窗口管理

管理窗口是另一项你需要掌握的核心技能. 老实说, 尽管章节介绍说它相当复杂, 但事实并非如此: 你只需要学习几个按键绑定. 使其复杂–或者对某些人来说, 简直令人恼火–的是首先对窗口的依赖, 以及如何适应窗口的概念.

让我们来看看你需要知道的按键绑定.

Key Binding Purpose
C-x 0 删除活动窗口
C-x 1 删除其他窗口
C-x 2 在下方拆分窗口
C-x 3 在右侧拆分窗口
C-x o 切换活动窗口

这五个按键是你使用, 拆分和删除窗口所需的全部按键. 正如你将在下面看到的, 还有更多命令, 但作为开始, 你可以使用这五个命令.

  1. 撤销窗口更改

    有时你想返回到过去的窗口配置. M-x winner-mode 这个模式会记住你的窗口设置, 并允许你分别使用 C-c <left> 和 C-c <right> 来撤销和重做. 要永久启用 Winner mode: M-x customize-option RET winner-mode RET

    Emacs 会平铺窗口, 通常会确保每个新窗口大致占据拆分窗口 (splitting window) 一半的屏幕空间. 如果你只有一个窗口并且向右拆分, 你现在就有两个窗口, 每个窗口占 50% 的份额.

  2. 删除窗口

    如果你使用 C-x 0, 那么 Emacs 会删除活动窗口–活动窗口总是光标所在的那个窗口–如果你键入 C-x 1, Emacs 会删除所有其他窗口.

  3. 拆分窗口

    窗口可以水平或垂直拆分 (或者说" 下方" 和" 右侧" ), 分别使用 C-x 2 和 C-x 3. 如果你有一个大显示器, 你可能想垂直拆分, 这样可以同时看到多个缓冲区; 你甚至可能更喜欢额外的细分. 我总是拆分成两个甚至四个窗口, 排列成一个 2x2 的网格.

    最后, 要在窗口之间移动, 请使用命令 C-x o. 我将其重新绑定到 M-o, 因为这是一个非常常见的操作. 你也应该这样做, 将以下内容添加到你的 init file 中:

    (global-set-key (kbd "M-o") 'other-window)
    
  4. 定向窗口选择

    有些人更喜欢 Emacs 自带的 windmove 包, 因为它可以让你朝基本方向移动, 而不是在所有窗口之间循环. 你可以通过将以下内容添加到你的 init file 来启用它:

    (windmove-default-keybindings)
    

    你现在可以用 shift 键切换窗口, 通过按 S-<left>, S-<right>, S-<up>, S-<down>.

  5. 使用其他窗口

    一旦你熟悉了拆分和删除窗口, 你就可以通过对其他窗口进行操作来在此基础上进行构建. 也就是说, 如果你想切换另一个窗口的缓冲区, 你必须先用 C-x o 切换到该窗口, 然后再用 C-x b 来更改缓冲区. 这有点乏味, 而且会打断你的节奏. 在这种情况下, 另一个窗口是指当你运行 C-x o 时紧挨着当前窗口的那个窗口.

    Key Binding Purpose
    C-x 4 C-f 在其他窗口中查找文件
    C-x 4 d 在其他窗口中打开 M-x dired
    C-x 4 C-o 在其他窗口中显示缓冲区
    C-x 4 b 在其他窗口中切换缓冲区并使其成为活动窗口
    C-x 4 0 Kill 缓冲区和窗口
    C-x 4 p 在其他窗口中运行项目命令

    这些是操作其他窗口最适用的命令. 还有一些其他的–你可以使用 C-x 4 C-h 来列出它们–但你不会经常使用它们.

    如果你仔细查看上表中的按键绑定, 你会发现 C-x 4 和 C-x 之间存在对称性–事实上, 它们在绑定和用途上几乎完全相同. 这并非巧合, 这种对称性将帮助你记住这些命令.

4.0.3. 框架管理

你可以创建框架 (frames)–在其他程序和窗口管理器中称为窗口 (windows)–如果你使用平铺窗口管理器或想利用多显示器设置, 你可能更喜欢这样做. 注意, 框架在终端 Emacs 中也有效.

用于框架的前缀键是 C-x 5. 与窗口的前缀键 (C-x 4) 类似, 命令也基本相同.

Key Binding Purpose
C-x 5 2 创建一个新框架
C-x 5 b 在其他框架中切换缓冲区
C-x 5 0 删除活动框架
C-x 5 1 删除其他框架
C-x 5 C-f 在其他框架中查找文件
C-x 5 p 在其他框架中运行项目命令
C-x 5 d 在其他框架中打开 M-x dired
C-x 5 C-o 在其他框架中显示缓冲区

使用多个框架切换缓冲区是无缝的, 如果一个缓冲区可见 (它显示在某个框架的某个窗口中), Emacs 会切换到该缓冲区已经可见的正确框架.

是否使用框架取决于你. 在 Emacs 中处理多个框架的机制有点笨拙, 因为所有框架共享同一个 Emacs 会话–这有时是福, 有时是祸. 我发现框架只有在多显示器设置中才能发挥其作用, 而在其他地方则不然. 我认为框架的用处受限于 Emacs 中已有的出色平铺窗口管理系统. 我唯一的建议是尝试一下, 看看框架是否适合你的工作流程.

4.0.4. 标签栏 (Tab Bars) 和标签行 (Tab Lines)

关于 Emacs 的一个常见抱怨是它缺少原生的" 标签栏" –就像网页浏览器和大多数其他编辑器中的那样–但在 Emacs 27 中, 他们添加了不止一个, 而是两个不同的实现来解决两个常见问题. 这姗姗来迟, 但实现和用户体验都很完善, 并且秉承 Emacs 的传统, 对第三方包作者和用户来说都是完全可定制的.

如果你不使用 Emacs 27, 你可以在包管理器中找到第三方实现: 它们是真实功能的仿制品, 依赖于 Emacs 渲染引擎的一个怪癖来提供真实功能的表象. 但是, 如果你是那种喜欢看到一行标签这种心智模型的人, 那么你可能会发现这两种实现都很有用.

  1. 颜色主题

    如果你使用的主题早于 Emacs 27, 你可能会发现主题的配色方案没有扩展到控制标签栏和标签行模式的新 faces. 你可以通过键入 M-x customize-apropos-faces tab- 来定制它们.

    有两种新的标签模式, 它们各自解决不同的问题.

  2. 标签栏模式 (Tab Bar Mode)

    标签栏按窗口配置 (window configurations) 对标签进行分组. 在 Emacs 中, 窗口配置是窗口的集合–大小, 位置, 缓冲区等等–代表 Emacs 框架的布局. 与 Emacs 中的大多数东西一样, 窗口配置是可配置的, 可以保存到磁盘, 寄存器 (稍后会详细介绍) 或者现在也可以保存到标签栏.

    在 IDE 中, 这可能被称为工作区 (workspaces) 或项目 (projects). 如果你经常发现自己在整个工作流程之间切换–比如一个 Org mode 日程和计划器以及你的电子邮件, 另一个用于你的编码–那么你就会有两个不同的窗口配置. 标签栏模式可以轻松地将你的想法组织到持久的窗口配置中.

    关于 Emacs 的一个常见抱怨–无论是新用户还是有经验的用户–是管理窗口的复杂性, 因为拆分系统以及缓冲区出现的位置不透明且难以理解.

    标签栏模式通过一个与我之前演示的框架, 缓冲区和窗口管理命令非常相似的直观界面来解决这种复杂性.

    要启用标签栏模式, 你可以自定义 M-x customize-option RET tab-bar-mode; 键入 M-x tab-bar-mode; 或者简单地调用下面列出的按键绑定之一.

    标签栏模式的前缀键是 C-x t.

    Key Binding Purpose
    C-x t 2 创建一个新标签
    C-x t 0 关闭当前标签
    C-x t RET 按名称选择标签
    C-x t o, C-<tab> 下一个标签
    C-S-<tab> 上一个标签
    C-x t r 重命名标签
    C-x t m 将标签向右移动一个位置
    C-x t p … 在其他标签中运行项目命令
    C-x t t 在其他标签中执行命令
    C-x t 1 关闭所有其他标签
    C-x t C-f, C-x t f 在其他标签中查找文件
    C-x t b 切换到其他标签中的缓冲区
    C-x t d 在其他标签中打开 Dired

    与框架, 窗口和缓冲区管理按键绑定类似, 标签栏绑定遵循相同的模式: 影响其他标签的操作类似于创建一个新标签.

    新标签根据创建它们的缓冲区以及之后的活动缓冲区来命名. 你可以根据自己的喜好重命名它们并在标签栏上移动它们. 如果你愿意, 可以使用 C-x t RET 按名称选择标签; 同样, 你可以使用 C-<tab>, C-S-<tab> 或鼠标切换到下一个或上一个标签.

    如果你不喜欢看到标签栏本身, 你可以使用 M-x customize-option RET tab-bar-show 来隐藏它, 并且仍然可以从标签栏的所有功能中受益.

    有一些未绑定到任何按键的命令.

    Command Purpose
    M-x tab-list 显示一个交互式标签列表
    M-x tab-undo 每次调用撤销一个已关闭的标签
    M-x tab-recent 切换到上次访问的标签

    我认为标签栏模式在你喜欢精心设计的窗口配置和一套简单的管理工具时会大放异彩. 通过包含查找文件, 切换缓冲区, 打开 dired 等常用实用功能, 它们可以让你快速从现有的标签栏配置跳转到一个新的配置. 将其与快速关闭, 重命名或跳转回的功能相结合, 你就可以像处理框架一样处理标签: 窗口及其状态的逻辑集合, 但没有管理多个框架的麻烦.

    然而, 窗口配置仍然经常会变得混乱: 前面在" 窗口管理" 中我谈到了 winner-mode, 一种撤销和重做最近窗口配置的方法. 对于标签栏来说, 它远没有那么有用, 因为它不理解特定于标签的窗口配置. 相反, 我建议你通过 M-x customize-option RET tab-bar-history-mode 来启用 M-x tab-bar-history-mode.

    标签栏历史模式管理特定于标签的窗口配置历史. 不幸的是, 遍历历史的命令没有绑定到任何东西. 下面的代码片段默认借用了 M-x winner-mode 使用的按键绑定. 如果你想同时使用两者, 你应该选择自己的按键绑定30.

  3. 标签行模式 (Tab Line Mode)

    与标签栏模式不同, 标签行模式的功能更类似于你在 Web 浏览器中找到的标签页. 与标签栏模式类似, 它的主要目的是将相关内容分组在一起: 默认情况下, 启用后, 标签行会列出该窗口中先前打开的缓冲区.

    要启用它, 请键入 M-x customize-option RET global-tab-line-mode; 或 M-x global-tab-line-mode.

    只有两个值得学习的按键绑定.

    Key Binding Purpose
    C-x <left> 选择上一个缓冲区
    C-x <right> 选择下一个缓冲区

    使用按键绑定, 你可以在与调用它的窗口相关的缓冲区列表中循环. 默认情况下, 这是一个最近缓冲区的列表, 但你可以使用 M-x customize-option tab-line-tabs-function 对其进行自定义, 例如, 将其限制为相同主模式的缓冲区.

4.0.5. 基本移动

  1. 导航键

    对你–实际上是对每个编辑器–可用的最基本的移动命令是简陋的箭头键. 它们如你所料地工作, 如果你是 Emacs 新手, 我建议你使用它们, 直到你学会更高级的移动命令.

    与其他编辑器一样, 你可以将箭头键–写作 <left>, <right>, <up> 和 <down>–与 control 键结合起来按词移动. 同时, 其他导航键, 如 page up 和 page down, 在 Emacs 中也同样有效.

    Key Binding Purpose
    <left>, … 箭头键按字符向四个方向移动
    C-<left>, … 同上, 但按词移动
    <insert> 插入键. 激活覆盖模式 (overwrite-mode)
    <delete> 删除键. 删除光标后的字符
    <prior>, <next> Page up 和 Page down 向上和向下移动近一整页
    <home>, <end> 移动到行首或行尾

    一旦你熟悉了 Emacs 的基础知识–处理缓冲区, 拆分和删除窗口, 保存和打开文件–你就应该停止使用导航键. 尽管它们很好用, 但它们离本位行 (home row) 太远了, 将右手从本位行移开仅仅为了在屏幕上移动光标是很耗时的.

    顺便一提 Page up/down 按钮会向上或向下滚动一屏文本, 保留 2 行文本作为上下文. 你可以通过直接在 init 文件中修改变量 next-screen-context-lines 或使用 Emacs 的 customize 界面来更改分页时重叠的量, 例如: M-x customize-option, 然后输入 next-screen-context-lines.

    如果你经常使用像 bash 或其他启用 GNU readline 的终端应用程序, 那么好消息是: 默认情况下它们使用 Emacs 风格的按键. 试试看, M-f 会按词向前移动. 事实上, GNU readline 中存在数十个 Emacs 最常用的命令31, 这意味着心智上下文切换很少, 并且每个使用 readline 的终端程序都支持它们.

  2. 按字符移动

    Emacs 中的箭头键等效项在你第一次遇到它们时会显得非常奇怪. 很多人想知道为什么 Emacs 会将像向前移动一个字符这样常见的操作绑定到 C-f. 事实是, 如果你了解 Emacs, 你几乎从不按字符移动.

    按字符移动–以及按行移动, 因为这在技术上是你可以向上或向下移动的最小单位–是你在缓冲区中可以进行的最小原子移动. 字符移动是为了精细操作; 如果你喜欢, 可以进行精确移动. 在缓冲区中按字符移动效率低下且乏味; 受限于键盘的重复速度或你输入的速度. 这很慢, 而且这种缓慢会累积起来, 因为我们花在移动上的时间通常和花在编辑文本上的时间一样多. 只有当它是最有效的可用命令时, 你才应该使用字符移动. 按词移动很棒, 但如果你想在一个词中移动 2 个字符, 那就帮不了你了.

    四个基本的移动命令是:

    Key Binding Purpose
    C-f 向前移动一个字符
    C-b 向后移动一个字符
    C-p 移动到上一行
    C-n 移动到下一行

    如你所见, 从助记的角度来看, 这些分配是有意义的–p 代表 previous (上一个), b 代表 backwards (向后)–并且它们都绑定在 C- 修饰键下.

    你可以对字符键应用通用参数 (universal arguments), 它们会如你所料地工作. 输入 C-8 C-f, 你会将光标向前移动八个字符. 你甚至可以结合负参数来反转命令的方向–这对于移动键可能没有多大意义, 但有些命令带有向前和向后命令. 大多数只在一个方向上起作用–即向前–而负参数是改变这个方向的唯一方法.

    学习 Emacs 自己的移动命令 (而不是使用键盘右侧的导航键) 在你了解 Emacs 其他移动命令的工作方式时就说得通了. 我见过经验丰富的 Emacs 用户执行精心设计的命令序列来完成有趣而复杂的操作,结果却在原地停下, 将右手从本位行移开, 按字符移动光标. 在某个时候, 你会意识到这是多么不协调 (以及它对你速度的影响有多大), 然后转而使用 Emacs 自己的命令.

  3. 按行移动

    <home> 和 <end> 键分别将光标移动到行首和行尾, Emacs 的等效键是 C-a 和 C-e. C-a 和 C-e 的行为与 <home> 和 <end> 完全相同 (事实上, 两组键都绑定到同一个命令), 但我会在下一章介绍行的定义.

    Key Binding Purpose
    C-a 将光标移动到行首
    C-e 将光标移动到行尾
    M-m 将光标移动到该行第一个非空白字符处

    最后一个命令 M-m, 对所有程序员都具有普遍的实用性. 当你键入 M-m 时, 光标会移动到行首, 然后向前移动, 直到遇到一个非空白字符:

    function foo() {
      return 42;█
    }
    

    输入 M-m 后:

    function foo() {return 42;
    }
    

    如果你在一行没有缩进的地方, 该命令只会转到行首. 这意味着在大多数情况下, M-m 比 C-a 更值得内化.

    1. 屏幕行, 逻辑行和可视行

      Emacs 默认会将长行换到窗口的右边缘, 但这引出了一个重要的问题: 当一行换行时, 它的开头和结尾在哪里?

      不幸的是, 答案需要一些解释, 而且术语? 嗯, 有点混乱:

      • 可视行 (Visual lines): 可视行定义为你所看到的. 如果你打开一个文件, 一长行在你的缓冲区中跨越三个换行, 那么你就有了三个可视行, Emacs 将每个可视行视为一个独立的, 不同的行, 即使底层文件只有一个.
      • 逻辑行 (Logical lines): 逻辑行与可视行相反. 逻辑行由缓冲区的内容决定, 而与其他因素无关; 无论是否换行, 文件中的一长行在 Emacs 中都被视为一长行.
      • 屏幕行 (Screen lines): 在 Emacs 文档的某些部分, 你可能会看到术语屏幕行与逻辑行一起使用. 屏幕行与可视行相同, 这两个术语可以互换使用.

      历史上, 当 Emacs 换行一个长行时, 用于向上或向下移动一行的 C-p 和 C-n 命令不会改变. 一个长行 (称为逻辑行) 换成三行 (称为可视行) 仍然算作移动一行 (逻辑行); 最终结果是你无法使用行命令按可视行移动. 这是否是正确的方式曾是一个引起分歧的问题, 的确如此. 你要么喜欢它……要么就修改 Emacs.

      而大多数人修改了 Emacs. 最终结果是, 如今, 在最新版本的 Emacs 中, 上一行/下一行命令按可视行移动. 你可以通过键入 M-x customize-option RET line-move-visual 来切换回旧的行为.

      更复杂的是 Visual Line Mode 的加入, 这是一个基于可视行概念并增加了额外功能的辅模式.

      Visual Line Mode 按词边界换行, 从而产生更" 整洁" 的换行效果, 就像你在传统文字处理器中看到的那样. 该辅模式还会禁用边缘指示器.

      此外, Visual Line Mode 会用可视等效项替换许多移动和编辑命令. C-p 和 C-n 的行为将与启用了 line-move-visual 选项的默认 Emacs 安装中的行为相同. 此外, 像移动到行首和行尾 (使用 C-a 和 C-e) 这样的命令现在作用于可视行而不是逻辑行. kill 命令 (绑定到 C-k, 但我们还没有介绍那个命令!) 也将作用于可视行.

      如果你想要这种行为–我鼓励你尝试一下, 看看它是否适合你的工作流程–你可以通过 M-x customize-option RET global-visual-line-mode 全局启用它, 或者通过键入 M-x visual-line-mode 在单个缓冲区中启用它.

      如果你不想要自动换行呢? 你可以通过 M-x toggle-truncate-lines 来切换自动换行–在 Emacs 中称为截断 (truncation).

    2. 显示行号和列号

      显示行号是一些人离不开的工作流程. 默认情况下它没有启用, 但你可以通过 M-x customize-option RET global-display-line-numbers-mode 或 M-x customize-option RET display-line-numbers-mode 来永久启用它.

      正如你所料, 该功能是可扩展的, 并带有许多默认的行号计数方法, 你可以使用 M-x customize-group RET display-line-numbers 进行配置. 相对和绝对行号都是可能的, 并且你可以配置许多显示选项来帮助计数和执行行上的命令.

      如果你只想查看当前所在的行号, 可以改为启用 M-x line-number-mode. 它会在模式行中显示光标所在行的当前行号. 同样, 也有 M-x column-number-mode 来显示当前行光标所在的列偏移量.

  4. 按词移动

    与字符移动类似, 按词移动几乎完全相同; 助记符与向后和向前字符的助记符相同, 只是将 C- 修饰符替换为 M-.

    Key Binding Purpose
    M-f 向前移动一个词
    M-b 向后移动一个词

    如果你使用过其他编辑器, 等效的箭头键是 C-<left> 和 C-<right>, 正如我之前提到的, 它们在 Emacs 中也可用. 在 Emacs 中, 单词移动在幕后相当复杂, 单词移动的确切行为取决于你使用的主模式.

    1. 什么构成一个词?

      什么是词? 简单地将其视为由空格分隔的一系列字符是大多数人的想法–因此也是期望–但在 Emacs 中, 事实要复杂得多.

      Emacs 中的模式编写者会对缓冲区中文本的性质做出假设. 你在 M-x text-mode 中编写的内容与你在 M-x python-mode 中编写的内容不同–并且处理方式也不同. 因此, 模式作者需要一种方法来说明在 text-mode 中句点 ' .' 是句子分隔符, 而在 Python 中是属性分隔符.

      的确, 每个字符–包括可打印字符和 Unicode 码点–都被模式作者直接或间接地赋予了含义, 在一个将字符映射到特定语法含义的注册表中. 这个注册表称为语法表 (syntax table), 我会多次提到这个概念来帮助你理解它如何影响移动和编辑, 但除此之外, 它只对 elisp 黑客和模式编写者感兴趣.

      语法表跟踪诸如" 哪些字符用于注释?" 或" 哪些字符构成一个词?" 之类的事情, 尽管从视图中隐藏, 但它影响着 Emacs 的每个部分.

      语法表本身决定了一个词 (或符号, 标点符号, 注释等) 作为语法单位的构成. 因此, 当你在屏幕上移动光标时, 它会根据语法表以及控制 forward-word 和 backward-word 的一般规则移动.

    2. 语法表

      每个编辑器都有与 Emacs 语法表等效的东西, 但 Emacs 与其他编辑器的不同之处在于你可以检查和更改语法表, 这反过来会影响当你在屏幕上调用某些命令时光标的移动方式. 你可以通过键入 C-h s 来查看当前缓冲区的语法表. 在其中, 你会看到字符及其分配的语法类的可读版本.

    3. 移动不对称性

      关于单词移动, 你还应该知道的一件事是它不是对称的: 键入 M-f 后跟 M-b–理论上应该让你回到原来的位置–并不能保证. Emacs 会巧妙地跳过它在你移动方向 (向前或向后) 上遇到的符号和标点符号.

      考虑当你键入 M-f 向前移动一个词时会发生什么:

      之前: Hello, █World.

      之后: Hello, World█.

      由于光标 █ 后面的字符都是字母字符, 单词命令的行为符合你的预期. 现在看看如果我们将光标移动到行尾并键入 M-b 向后移动一个词会发生什么:

      之前: Hello, World.█

      之后: Hello, █World.

      单词命令足够聪明, 能够意识到, 尽管句号不是单词字符, 但它应该简单地忽略它, 因为标点符号前面紧挨着一个单词. 在我们键入 M-b 之后再键入 M-f 并不会让我们回到开始的位置:

      之前: Hello, █World.

      之后: Hello, World█.

      这再次印证了我的观点, 即单词命令是不对称的. 这需要一些时间来适应. Emacs 通常会忽略你移动方向上紧跟在光标后面的非单词字符. 例如, 我们跳过了句点 ., 因为它是一个非单词字符, 并且是光标向后移动时遇到的第一个字符. 这种行为的原因很简单: 如果 Emacs 不这样做, 那么单词命令在文本和代码中遇到的每个非单词字符都会算作一个独立的单词并结束移动命令.

      这是一个更极端的例子–但你很可能在源代码中遇到:

      print(add_two(num_table[10]))

      上面的光标在行尾, 如果你键入 M-b 并向后移动一个词, 你会停在 10 的前面:

      print(add_two(num_table[█10]))
      

      这是因为, 和以前一样, Emacs 会忽略符号和标点符号, 当且仅当它在遇到单词字符之前遇到它们. 再次向前移动并不会将我们带到行尾, 因为我们已经在一个词上了:

      print(add_two(num_table[10█]))
      

      所以, 你可能想知道为什么这是一件好事. 首先, 你可以在原来的 M-b 之后跟上 M-d 来 kill 数字 10, 由于不对称性, 你不会 kill ])) 符号 (但稍后会详细介绍 kill 命令). 另一个原因是, 将单词视为仅由空格分隔是没有意义的–这会产生太多问题. 如果一行中有很多空格怎么办, 标点符号和符号又如何处理呢? 当你需要处理像大多数源代码那样混合了符号和文本的情况时, Emacs 的行为是完全合理的; 不断按 M-b 可以向后移动连续的文本单词, 但你会方便地跳过在移动方向上遇到的任何符号. 令人困惑的一点是不对称性; 规则看起来不合理–但现在你知道 Emacs 是如何移动的, 它的行为应该更有意义了.

    4. 子词和超词移动

      如果你编辑大量使用驼峰命名法 (CamelCase) 的代码, 你可能希望你的移动和编辑命令将每个子词–由大写字母分隔–视为独立的词. 同时, 你可能希望相反的情况: 即 Emacs 的单词移动命令通常会将 textwrittenlikethis (但这又完全取决于语法表和主模式的特性) 视为三个独立的词 (written, like, 和 this), 而不是一个词.

      Command Purpose
      M-x subword-mode 将驼峰命名法视为不同单词的辅模式
      M-x superword-mode 将下划线命名法 (snakecase) 视为一个单词的辅模式
      1. 全局辅模式

        两者都有可用的全局模式, 你可以通过键入以下内容来启用它们: M-x customize-option global-subword-mode M-x customize-option global-superword-mode

        当你启用 M-x subword-mode 时, 你会启用特殊的移动, 转置和 kill 命令, 这些命令作用于驼峰命名法中的每个独立大写单词. 如果你编写大量使用驼峰命名法的语言代码, 你会喜欢 subword mode.

      2. Glasses mode

        有一个异想天开的辅模式, M-x glasses-mode, 它会在视觉上 (它不会改变你的缓冲区文本) 将驼峰命名法 (CamelCase) 的单词分隔成 CamelCase.

        superword 命令, M-x superword-mode, 与此类似但作用相反: 它会重新连接符号 (通常但不总是包括下划线), 使它们被视为单词的一部分. 注意这个命令并不完美. 主模式作者决定像 _ 或 . 这样的字符应该属于哪个语法类, 如果他们没有将像 _ 这样的字符设置成符号, 那么这个命令将不起作用.

  5. 按 S-表达式移动

    也许 Emacs 中最有用–但未被充分利用–的功能是按 S-表达式 (s-expression) (或简称 sexp) 移动的能力. 这个神秘的名称值得解释一下: 它是一个 LISP 术语, 如今涵盖了作用于平衡表达式 (balanced expressions) 的各种命令.

    平衡表达式通常包括:

    • 字符串 (Strings): 编程语言是字符串的主要例子, 字符串是平衡表达式, 因为它们以 " 或 ' 开头和结尾.
    • 括号 (Brackets): 在大多数主模式中, 括号被认为是平衡的, 因为它们定义了开闭字符: [ 和 ], ( 和 ), { 和 }, < 和 >.

    平衡表达式可以跨越多行–例如多行字符串–Emacs 知道这一点.

    一组特定的字符是否定义了一个平衡表达式取决于你的主模式, 而主模式又会在我之前谈到的语法表中定义这些特征.

    与单词和字符命令类似, 这些命令遵循与之前相同的助记符, 但使用了不同的修饰符. 这次是 C-M-.

    Key Binding Purpose
    C-M-f 按 S-表达式向前移动
    C-M-b 按 S-表达式向后移动

    我认为这些命令是程序员学习最重要的命令之一. 看看当你按 C-M-f 时光标移动到哪里:

    d = █{
      'Hello': 'World',
      'Foo': 'Bar',
    }
    

    之后:

    d = {
      'Hello': 'World',
      'Foo': 'Bar',
    }

    Emacs 知道 { 和 } 在 python-mode 中是一个平衡表达式–因为语法表–因此将 { 和 } 视为一个平衡表达式, 并在你键入 C-M-f 时立即移动到结束括号.

    一旦你开始从平衡表达式的角度思考代码, 你会发现它们无处不在. 不仅仅是在 LISP 中你会发现它们有用; 几乎所有主模式都充满了平衡表达式–还有一个额外的好处是, 当你对像普通文本这样的" 非平衡" 表达式调用它们时, S-表达式移动命令的行为就像单词命令一样.

    学习如何使用这些命令绝对至关重要.

    1. 下移和上移列表
      Key Binding Purpose
      C-M-d 向下进入列表
      C-M-u 向上移出列表

      与 S-表达式移动命令类似, 列表命令最初是为 LISP 设计的, 但在 LISP 之外也找到了用武之地. 当你按 C-M-d 时, 光标会跳转到当前光标位置之前的最近的圆括号平衡表达式内部:

      之前:

      █result = foo(bar())
      

      之后:

      result = foo(█bar())
      

      光标会移动到最近的平衡表达式内部. 为此, 光标会跳转任意距离, 重复调用会深入到嵌套结构中, 相反, C-M-u 会向上移动. 与单词命令类似, 列表命令不是对称的; 向上移动会让你上升一级, 但会将光标停留在起始字符处:

      之前:

      result = foo(bar())
      

      之后:

      result = foo(bar█())
      
      1. 移出字符串

        在较新版本的 Emacs 中, 你可以在字符串内部使用 C-M-u 来跳转到起始引号.

        就其本身而言, 这些命令除了在" 列表" 表达式中跳入跳出之外几乎没什么用, 但要意识到将这种行为与另一个命令 kill-sexp32 结合起来, 将会 kill 光标前面的平衡表达式–所以键入 C-M-u

    2. 向前和向后列表

      另外两个功能性导航辅助工具是 C-M-n 和 C-M-p. 它们会在相同的嵌套级别移动到下一个或上一个列表表达式.

      Key Binding Purpose
      C-M-n 移动到下一个列表
      C-M-p 移动到上一个列表

      例如, 以下是你重复键入 C-M-n 时发生的情况:

      (+ █(* 5 2) (- 10 10))
      
      (+ (* 5 2)(- 10 10))
      
      (+ (* 5 2)(- 10 10))
      
      (+ (* 5 2) (- 10 10))
      

      如你所见, 它从一个表达式移动到下一个表达式, 这包括平衡表达式的开始和结束. 再次键入 C-M-n 会产生一个错误: 我们已经到达了这个嵌套级别的平衡表达式的末尾. 如果我们键入 C-M-u 来向上移动列表:

      (+ (* 5 2) (- 10 10))
      

      现在, 我们移出了嵌套表达式并进入其父表达式. 随后调用 C-M-n 会将我们带到平衡表达式的末尾:

      (+ (* 5 2) (- 10 10))

      对于 LISP, 这些命令非常宝贵. 嵌套的圆括号表示层级结构, 因此 LISP 黑客需要一套高效的工具来在平衡表达式中上下左右移动. 对于所有其他编程语言, 其效用完全取决于你遇到平衡表达式的频率. 在大多数语言中–比如 C, Java, Python 或 JavaScript–它们非常有用. 而且这是一种在像花括号或方括号这样的平衡表达式之间优雅移动的方式.

  6. 其他移动命令

    我认为按字符, 行, 词和 S-表达式移动是最重要的移动命令. 它们在各种编辑任务中具有最大的实用性–特别是编程和文本编辑–但也有一些移动命令最适合特定任务–它们是否适合你完全取决于你做什么.

    1. 按段落移动
      Key Binding Purpose
      M-} 向前移动到段落末尾
      M-{ 向后移动到段落开头

      段落的定义取决于你问谁以及你的个人风格, Emacs 试图迎合大多数人. 段落命令本身依赖于一组定义段落开头和结尾的变量:

      Variable Name Purpose
      paragraph-start 使用一个大型正则表达式定义段落的开头
      paragraph-separate 将段落分隔符定义为一个正则表达式
      use-hard-newlines 由命令 M-x use-hard-newlines 设置, 并定义硬换行符是否定义段落

      我建议你描述这些变量 (使用 C-h v) 以更好地了解 Emacs 的段落系统是如何工作的. 特别是 paragraph-start 变量, 它是一堆试图为每个人做所有事情的正则表达式. 默认情况下, 当你使用 M-} 和 M-{ 时, Emacs 会将以换行符分隔的文本块视为一个段落.

      你可以通过运行 M-x paragraph-indent-minor-mode 来改变段落命令的行为, 使前导空格标记新段落的开始.

      段落命令也用于编程模式. 许多程序员将代码行组合在一起, 并用空行将它们彼此分开, 这使得它们成为段落命令的理想候选者.

    2. 按句子移动

      句子命令与行命令具有对称性, 只是将 C- 修饰符替换为 M-:

      Key Binding Purpose
      M-a 移动到句子开头
      M-e 移动到句子末尾

      与段落类似, 句子的定义是一种因人而异的风格, 但 Emacs 假设你在句号后用两个空格开始你的句子:

      This is one sentence. This is another.

      你可以通过自定义 (使用 M-x customize-option) 以下变量来更改此行为:

      Variable Name Purpose
      sentence-end-double-space 非 nil 表示单个空格不结束句子.
      sentence-end-without-period 非 nil 表示句子将在没有句号的情况下结束.
      sentence-end-without-space 结束句子而不需要在后面加空格的字符串.

      你最可能自定义的是 sentence-end-double-space.

    3. 按 Defun 移动

      defun 这个词是 LISP 的另一个行话, 代表 define function–在 Emacs 中, 你会在命令作用于函数的地方看到它.

      与句子和行命令类似, defun 命令使用 C-M- 作为其修饰符:

      Key Binding Purpose
      C-M-a 移动到 defun 的开头
      C-M-e 移动到 defun 的末尾

      defun 命令会移动到光标所在函数的逻辑开头或结尾. 我必须指出, 函数 (function) 这个词实际上是一个相当松散的术语. 它不一定非得是函数, 但在编程模式中, 它通常是函数, 类或两者兼而有之; 对于其他模式, 它可能会做其他事情–例如, 在 reStructuredText 中, 它会跳转到节或章的开头和结尾.

      如果你想 (比如说) 快速更改编程语言中函数的名称或函数参数, 那么移动到 defun 的开头非常有用.

      考虑光标的位置:

      int addtwo(int x)
      {
        return x + 2█;
      }
      

      按 C-M-a 会将我们带到 defun addtwo 的开头:

      int addtwo(int x)
      {
        return x + 2;
      }
      

      随后调用 C-M-a 会将你进一步" 向上移动链条" , 可能会移动到父块, 或者如果你在根目录, 则移动到文件顶部.

    4. 按页移动

      Emacs 中的页 (page) 与现实生活中的页的概念只有一点点关系. 在 Emacs 中, 页是由变量 page-delimiter 中定义的字符分隔的任何内容, 默认情况下是控制代码 ^L–更广为人知的是 ASCII 控制代码换页符 (form feed). 你不太可能使用这些命令, 所以我不担心记住它们.

      在一些 LISP 圈子里, 按页对事物进行分组很常见, 由于 Emacs 与 LISP 社区关系密切, 它自带了一系列用于与页交互的命令.

      Key Binding Purpose
      C-x ] 向前移动一页
      C-x [ 向后移动一页
      1. 发现页面命令

        以下是查找页面命令的一种方法: C-h a (用于 M-x apropos-command), 然后搜索 page$ 以查找所有以单词" page" 结尾的命令.

        知道如何向 Emacs 提出正确的问题–使用 apropos 或 describe system–是掌握 Emacs 的基石.

4.0.6. 滚动

与箭头键类似, <prior> 和 <next> 命令 (分别为 Pg. Up 和 Pg. Down) 也有其 Emacs 等效项, 但 Emacs 的滚动机制与众不同, 以至于有些人觉得它令人沮丧. 这是因为 Emacs 会滚动近乎整个屏幕, 其中整个屏幕是该窗口中可见的行数. 为了帮助你在滚动时保持连续性, Emacs 会保留两三行 (由变量 next-screen-context-lines 控制), 这样你就不会迷失方向.

Key Binding Purpose
C-v 向下滚动近一整屏
M-v 向上滚动近一整屏
C-M-v 向下滚动另一个窗口
C-M-S-v 向上滚动另一个窗口

C-v 和 M-v 的工作方式与导航键 <prior> 和 <next> 相同.

奇怪的是那两个滚动另一个窗口的命令. 它有其用途. 我几乎总是使用多个窗口, 并且能够滚动另一个窗口–包含帮助缓冲区, 日志文件甚至另一个源代码文件–对我来说是很常见的操作.

大多数编辑器缺乏此功能; 相反, 你必须:

  • 使用鼠标: 用鼠标移到窗口上, 最后使用滚轮向上或向下滚动, 或者单击并按 <prior> 和 <next>.
  • 使用键盘: 切换到另一个拆分窗口或选项卡, 使用 <prior> 或 <next> 向上或向下滚动.

在 Emacs 中, 你可以使用其他窗口滚动命令.

我不经常使用 C-M-S-v. 我发现键入 C-M– C-M-v 来反转滚动方向 (使用负参数) 比键入 C-M-S-v 更容易. 后一个命令需要特别灵巧的手指操作, 如果你滚动得太远, 你必须交换手指位置才能键入 C-M-v. 使用 C-M– 和 C-M-v 要容易得多.

  1. 保持节奏

    注意负参数命令 C-M– 与 C-M-v 方便地绑定到相同的修饰键. 正如我在关于通用参数的章节中解释的那样, 这并非巧合. 通过将参数命令绑定到所有主要修饰键组合, 你不必在命令之间扭曲手指来为命令添加前缀参数.

    你也可以水平滚动–或者在 Emacs 的行话中只是向左和向右滚动:

    Key Binding Purpose
    C-x < 向左滚动
    C-<next> 向左滚动
    C-<prior> 向右滚动
    C-x > 向右滚动
    S-<wheel> 用鼠标滚轮向左/右滚动 (Emacs 28)

    如果你编辑大量带有很长行的文本文件–例如 CSV 文件–你可能需要先禁用自动换行 (在 Emacs 中称为行截断), 方法是使用 M-x toggle-truncate-lines. 除非你真的需要它们, 否则我不会费心去记水平滚动命令.

    当然, 你仍然可以使用鼠标滚轮进行滚动, 不过它是否在终端中起作用取决于你的系统.

    现在介绍另外两个用于四处移动的命令, 即跳转到缓冲区开头和结尾的功能:

    Key Binding Purpose
    M-< 移动到缓冲区开头
    M-> 移动到缓冲区末尾

    当你移动到缓冲区的开头或结尾时, Emacs 会在你原来的位置放置一个标记 (mark)–一个不可见的位置标记–这样你就可以返回到原来的位置. 例如, 如果你键入 M-< 跳转到缓冲区的开头, 你可以键入 C-u C-<SPC> 返回. 正如你记得的那样, C-u 是通用参数; 在本例中, 它设置一个标志, 以便当你键入 C-<SPC> 时, Emacs 会将其解释为跳转到上一个标记. 标记及其在 Emacs 中的用途是我稍后会讨论的一个主题.

4.0.7. 书签 (Bookmarks) 和寄存器 (Registers)

Emacs 中的书签与 Web 浏览器中的书签工作方式完全相同, 但有一个显著的例外, 即支持更广泛的来源. 这使得 Emacs 的书签系统足够灵活, 可以让你为文件, M-x dired 目录, M-x man 页面, Org mode, DocView (包括 PDF 文件) 和 info 手册页面添加书签. 由于 Emacs 的 TRAMP 系统, 还可以为远程文件和目录添加书签以便快速访问.

Emacs 中的书签是永久性的, 这意味着它们会自动保存到 ~/.emacs.d/ 中一个名为 bookmarks 的书签文件中.

  1. 书签文件

    变量 bookmark-default-file 决定了 Emacs 存储书签的位置. 该文件是纯文本 (实际上是 elisp S-表达式), 这意味着可以手动编辑它 (如果你绝对必须这样做), 或者如果你经常在多台机器上添加或删除书签, 则可以合并这些文件.

    Key Binding Purpose
    C-x r m 设置书签
    C-x r l 列出书签
    C-x r b 跳转到书签

    书签是跳转到常用文件或目录的一种非常有效的方式. 如果 Emacs 手册中有你想要经常返回的部分, 书签也同样有效. 由于 Emacs 的统一性–即缓冲区–这三者可以无缝地存储在同一个书签列表中并从中调用.

    然而, 寄存器 (Registers) 则不同; 它们是硬币的另一面–书签是永久性的, 而寄存器是瞬态的. 寄存器是一个单字符的存储和调用机制, 用于多种类型的数据, 包括:

    • 窗口配置和框架集: 你可以存储和调用窗口配置的布局, 不过我认为有更好的工具 (例如我在" 窗口管理" 和" 标签栏模式" 中谈到的 M-x winner-mode) 来完成这项工作. 框架集与窗口配置相同, 但保存的是 Emacs 框架的信息.
    • 点 (Points): 点的位置是你可以存储在寄存器中的另一项内容. 如果你习惯了其他 IDE 或编辑器中基于行的书签, 那么这些是 Emacs 中最接近的等价物. 不幸的是, 按键绑定 (如下所示) 削弱了它们的用处.
    • 数字和文本: 纯文本也是可存储的. 如果你想插入多段文本, 使得 kill ring 成为不太理想的选择. 你也可以存储数字, 尽管文本和数字之间唯一的区别是能够对包含数字的寄存器进行简单的算术运算 (加法).
    Key Binding Purpose
    C-x r n 在寄存器中存储数字
    C-x r s 在寄存器中存储区域
    C-x r SPC 在寄存器中存储点
    C-x r + 增加寄存器中的数字
    C-x r j 跳转到寄存器
    C-x r i 插入寄存器的内容
    C-x r w 在寄存器中存储窗口配置
    C-x r f 在寄存器中存储框架集

    寄存器只能是单个字符. 当你想存储或调用某些内容时, 系统会要求你输入单个字符进行查询. Emacs 会在 register-preview-delay 秒后弹出一个预览窗口, 其中包含已知寄存器及其内容的快速概览.

    C-x r s 是我最常用的一个. 它将区域存储在寄存器中–非常实用, 很可能也是你最常做的–而 C-x r i 则在光标处插入寄存器的内容. 默认情况下, 光标位于插入文本之后. 如果你给它一个前缀参数 (C-u C-x r i), 它会改为将光标放在前面.

    你可以用 C-x r SPC 存储光标位置, 但要跳转到它, 你必须使用 C-x r j; 可以说 C-x r i 应该在这里做正确的事情, 如果寄存器存储的是一个点, 就跳转到该点. 相反, Emacs 会插入内部光标位置, 这对任何人都没有用处.

    如果你想存储和调用窗口或框架布局–对任何喜欢特定窗口排列的人都有好处–你也可以在寄存器中存储窗口或框架配置.

    要存储一个数字, 将光标放在它前面并键入 C-x r n. 要按 prefix-numeric-value (默认为 1) 递增它, 请键入 C-x r +; 要按任意数量递增它, 请给它一个数字参数 (以及一个负数参数来递减). 你可以使用 C-x r i 调用数字寄存器.

    寄存器和书签各有其用武之地, 但它们服务于两个不同的目的. 我会专注于记住书签命令, 因为它们更有可能是你日常使用的东西.

4.0.8. 选区 (Selections) 和区域 (Regions)

选择文本是一项常见的操作, 但在 Emacs 的 info 文档和 describe 系统中, 它被称为区域 (region). 正如我在" 点和标记" 中提到的, 区域边界由点 (point) 和标记 (mark) 组成.

其他编辑器对区域的开始和结束几乎没有或根本没有区别, 但在 Emacs 中, 这种区别相当重要. 区域始终定义为点和标记之间的连续文本块.

为了进行可视化演示, 请在 Emacs 中尝试以下操作: 按 C-<SPC>. 在回显区, 会出现一条消息, 内容为" Mark Set." 现在, 在缓冲区中移动光标–使用箭头键或我之前介绍的其他移动命令–并观察区域如何变化, 因为它现在已被激活. 再次按 C-<SPC>–或通用的" 摆脱困境" 命令 C-g–来取消激活该区域. 请注意, 区域始终定义为标记到光标, 无论光标是在标记之前还是之后. 因此, 此功能类似于你在其他编辑器中按住 shift 并使用箭头键移动时的操作.

因此, 当你进行可视化选择时, 你正在使用 Emacs 的 Transient Mark Mode, 也称为 TMM. TMM 在 Emacs 历史上的出现远比你想象的要晚; 事实上, 它直到最近才默认开启.

那么 TMM 之前是什么样的呢? 首先, 你根本没有可视化高亮–所以你必须记住你把标记放在哪里了. 而且很多命令根本不知道区域之类的东西. 像 M-x replace-string 这样在缓冲区中进行简单字符串替换的简单命令, 总是从光标处一直替换到缓冲区末尾, 没有任何例外. 因此, 如果你想修改缓冲区的特定部分, 你必须使用 Emacs 神秘的 narrowing 命令, 将缓冲区的可见内容缩小到你希望命令作用的范围. 正如你可以想象的那样, 这对初学者学习 Emacs 没有任何帮助.

所以, 今天你不用担心区域缩小 (除非是专门的编辑), 也不用记住标记的位置, 因为 TMM 会向你显示区域.

然而, TMM 和 Emacs 区域系统的结合并非完美. Emacs 中的标记不仅仅用于区域. 它是缓冲区中跳转的重要工具, 因为一些会将你从当前位置带走的命令会在标记环 (mark ring) 上留下一个标记 (实际上是一个面包屑路径), 你稍后可以返回. 一个例子是 M-< 和 M->–用于跳转到缓冲区开头和结尾的命令–它们都会在你跳转之前标记你的旧位置, 这样你以后就可以通过键入 C-u C-<SPC> 返回到你的旧位置.

  1. 标记环 (mark ring)

    与撤销环 (undo ring) 类似, 标记环包含你在缓冲区中放置的所有标记–既有直接使用像 C-<SPC> 这样的标记命令放置的, 也有间接通过像 M-< 和 M-> 这样的命令放置的. 还有一个用于跨缓冲区边界工作的命令的全局标记环 (global mark ring). 你可以通过回显区中出现的文本 Mark set (或其变体) 来判断标记环何时发生了更改.

    因此, 命令 C-<SPC> 会设置标记, 并且在启用 TMM 的情况下, 当你移动光标时它还会激活区域高亮. 这意味着如果你只是想设置标记以便稍后可以返回 (使用 C-u C-<SPC>), 你必须按 C-<SPC> C-<SPC>–一次设置标记, 再一次取消激活区域. 另一个需要注意的问题是, Emacs 中一些作用于区域的命令–文本替换, 将区域中的文本更改为大写等等–即使区域未激活也同样有效.

    以下是激活选区和跳转到标记所需的按键. 如果你是 Emacs 新手, 在熟悉 Emacs 自己的选择机制之前, 请随意使用 shift 选择键.

    Key Binding Purpose
    C-<SPC> 设置标记, 并切换区域
    C-u C-<SPC> 跳转到标记, 重复调用会进一步回溯标记环
    S+<left>, … 与其他编辑器类似的 Shift 选择
    C-x C-x 交换点和标记, 并重新激活你最后的区域

    C-x C-x 命令 (称为 exchange-point-and-mark)很有趣. 它从光标处–即你在缓冲区中的当前位置–重新激活区域, 无论标记在哪里; 然后, 它会交换光标和标记的位置. 当你想重新激活最后一个区域或者只是想交换标记和光标的位置时, 这个命令很有用. 如果你想在标记或光标附近编辑文本, 或者只是想重新激活最后一个标记和光标之间的区域, 交换光标和标记就很有用.

    让我们用一个简单的规则列表来结束:

    1. 区域 (region) 是由点 (point) 和标记 (mark) 界定的连续块.
    2. 你用 C-<SPC> 激活一个区域, 它会设置标记然后激活该区域 (如果你使用 TMM, 而且你应该使用!). 再次按 C-<SPC> 会取消激活该区域.
    3. 当你四处移动时, 活动区域会跟随光标, 但当你使用非移动命令时会中断.
    4. 标记具有双重作用, 作为一个信标, 你可以用 C-u C-<SPC> 返回, 即使是你用 C-<SPC> 设置的标记. 重复调用 C-u C-<SPC> 会进一步回溯标记环.
    5. 用 C-x C-x 交换点和标记会重新激活该区域并切换你的点和标记的位置.
    6. 某些 Emacs 命令不关心区域是否实际激活, 并且无论如何都会起作用 (所以要小心).

    与往常一样, 我鼓励你及时学习 Emacs 自己的命令, 但如果你不知所措, 可以使用鼠标单击拖动选择, 或使用 S+<arrow key> 进行箭头键选择.

  2. 选区兼容模式

    为了简化向 Emacs 的过渡, 你可以启用几个辅助模式来模仿其他编辑器的行为. Emacs 现在默认启用其中一两个, 作为其现代化 Emacs 的一部分. 我的个人建议是从你熟悉的内容开始, 随着你 Emacs 技能的提高, 逐渐摆脱兼容模式.

    • M-x delete-selection-mode: 当区域处于活动状态并且你向缓冲区键入文本时, Emacs 会首先删除选定的文本. 此行为模仿了大多数其他编辑器. 要启用 (或禁用) 它, 请使用 customize 界面: M-x customize-option RET delete-selection-mode
    • shift-select-mode (变量, 默认启用): Shift 组合移动键–包括传统的导航键 (如箭头键) 和 Emacs 自己的命令–会激活区域并沿你移动的方向扩展它. shift 选择与使用 C-<SPC> 设置标记的方式不同. 当你使用 shift 选择一个区域时, 任何非 shift 组合的移动命令都会取消激活该区域. 与 delete-selection-mode 类似, 此功能模仿了其他编辑器中的行为. 例如, S-<left>, S-<right> 等会一次选择一个字符, 而 C-S-f 也会这样做, 但使用的是 Emacs 自己的移动命令. 要禁用 (或启用) 它, 请使用 customize 界面: M-x customize-option RET shift-select-mode
    • M-x cua-mode: 可能与 Emacs 的选择和剪贴板系统最根本的区别是 CUA mode. 以 IBM 的 Common User Access 命名, cua-mode 允许你使用 C-z, C-x, C-c 和 C-v 来像在其他程序中一样进行撤销, 剪切, 复制和粘贴. 由于 CUA mode 与 Emacs 自己的前缀键绑定 C-x 和 C-c 冲突, 因此 CUA mode 默认是禁用的. 如果你启用它, 前缀键 C-x 和 C-c 仍然有效, 但有一些小的副作用和额外的限制:

      1. 要在活动区域内键入前缀 C-x 或 C-c, 你必须快速连续地双击前缀键 (例如, C-x C-x).
      2. 要键入前缀键 C-x 或 C-c 后跟另一个键, 你必须快速连续地键入它们, 否则会触发剪贴板命令.
      3. 或者, 对于第 1 点和第 2 点, 你可以使用 S- 修饰符键入前缀键: C-S-x 替换 C-x, C-S-c 替换 C-c.

      CUA mode 是那些会决定某些人是否采用 Emacs 的生活质量特性之一. 如果你是其中之一, 务必启用它! 你总是可以随着时间的推移逐渐摆脱 CUA 键, 或者干脆忍受我提到的副作用. 要启用 (或禁用) 它, 请使用 customize 界面: M-x customize-option RET cua-mode

    我的个人建议是学习 Emacs 自己的区域命令 (稍后会详细介绍), 因为 Emacs 从未围绕 CUA 模式的思想进行设计33. 话虽如此, 消除入门障碍–这是 Emacs 维护者正在努力的事情–对于新 Emacs 用户来说在短期内更为重要.

  3. 设置标记 (Mark)

    我已经向你展示了如何使用箭头键 (S+<arrow key>) 和 C-<SPC> 以交互方式激活标记, 但 Emacs 有许多作用于语法单位 (syntactic units) 的标记命令, 正如你之前可能记得的那样, 它们是诸如单词, S-表达式和段落之类的东西.

    使用 C-<SPC> 设置标记是激活区域的主要方法, 但使用起来很麻烦. 你必须设置标记, 移动到所需位置, 然后运行命令. 更糟糕的是, 它会打断你的节奏.

    如果你想进行精确选择, 最好使用 Emacs 专用的标记命令:

    Key Binding Purpose
    M-h 标记下一个段落
    C-x h 标记整个缓冲区
    C-M-h 标记下一个 defun
    C-x C-p 标记下一页
    M-@ 标记下一个词
    C-M-<SPC> 和 C-M-@ 标记下一个 S-表达式
    C-<SPC>, C-g 取消激活区域

    所有标记命令都会附加到现有选区 (如果已激活区域). 因此, 如果你想连续标记两个词, 你只需按两次 M-@, 或者将其与数字参数结合使用: M-2 M-@. 同样, 你可以使用负参数修饰符来反转方向.

    附加到现有区域的实现很简单: Emacs 只是将标记从一个位置移动到下一个位置. 这使得执行诸如删除选区或对其执行命令之类的局部编辑变得容易, 并且你可以根据需要将任意多个不同的标记命令链接在一起, 直到标记了所有想要标记的内容.

    在" 什么构成单词?" 一章中, 我谈到了语法表以及主模式的语法表如何影响移动命令. 许多标记命令是类似的, 特别是 M-x mark-word 命令 M-@. 然而, 某些标记命令在某些主模式中被覆盖, 以便它们在该特定模式下正确工作. 例如, 在 reStructuredText 中, 绑定到 C-M-h 的 M-x mark-defun 命令会选择整个章节; 这是合理的, 因为文本文件中没有 defuns (LISP 中函数的术语). 并非所有主模式都支持 M-x mark-defun, 但 Emacs 附带的大多数模式都支持–最终取决于主模式的作者来告诉 Emacs 如何标记像 defuns 这样的复杂事物.

    1. 取消激活区域

      记住, 你可以用 C-<SPC> 或 C-g (Emacs 的通用退出命令) 来取消激活区域.

      我建议你忽略 C-x C-p (标记下一页的按键绑定). 专注于记住 C-x h, 因为它会标记整个缓冲区; C-M-h, 因为它会标记 defun; 以及 C-M-<SPC>, 因为它会按 S-表达式标记, 并且在大多数情况下, 如果遇到单词, 其行为也相同.

      C-M-<SPC> 是我最常用的命令之一. 将其与负参数 (C-M– C-M-<SPC>) 结合使用可以反转方向, 你也可以轻松地反向标记 S-表达式.

      最后, 如果你随后使用 kill34 命令, 许多手动标记都是多余的, 因为 Emacs 有自己的 kill 命令, 可以直接作用于语法单位. 我将在<编辑理论>中更详细地介绍 kill 命令.

4.0.9. 搜索和索引

基本移动命令主要作用于语法单位. 它们的主要目的是作为从 A 点移动到 B 点的越来越精确的工具–从按整个段落或 defun 导航到按单个字符移动.

然而, 通常情况下, 你希望在缓冲区中搜索文本. 自然地, 这是 Emacs 擅长的事情. Emacs 的独特之处可能在于文本搜索应该像其他所有内容一样快速和简化的理念. 正如你很快就会看到的, 这意味着 Emacs 的搜索功能同样适用于导航.

  1. Isearch: 增量搜索

    Emacs 的增量搜索–或者简称 Isearch–是一个极其强大的搜索功能, 绑定到 C-s, 你会在你的 Emacs 生涯中大量使用它. 在其简单的外表之下, 是一套复杂的辅助命令:

    Key Binding Purpose
    C-s 开始增量搜索
    C-r 开始向后增量搜索
    C-M-s 开始正则表达式增量搜索
    C-M-r 开始向后正则表达式增量搜索
    RET 选择匹配项
    C-g 退出 Isearch
    M-<, M-> 跳转到第一个或最后一个匹配项 (Emacs 28)
    C-v, M-v 跳转到当前不可见的下一个或上一个匹配项 (Emacs 28)

    使用 Isearch 很简单:

    • 选择搜索方向: 你可以使用 C-s 或 C-r 开始向前或向后 Isearch, 如上表所示. 微缓冲区会显示 I-search: 或 I-search backward:.

    如果之前搜索过某些内容, 你可以通过重复 Isearch 命令来调用上一个搜索词. 因此, 如果你想调用上一个搜索词, 你可以键入 C-s C-s 来首先打开 Isearch, 然后调用上一个搜索字符串. Emacs 会在你执行此操作时自动进行增量搜索.

    • 开始输入: 你按下的每个键都会触发增量搜索引擎, 以在你搜索的方向上查找与你的搜索字符串匹配的第一个匹配项. 当它遇到第一个匹配项时, 增量搜索引擎会高亮显示缓冲区中该搜索字符串的所有其他匹配项; 如果你的搜索字符串没有匹配项, Emacs 会在匹配到尽可能多的内容后停止, 并告诉你失败了.
    • 浏览匹配项: 如果有多个匹配项, 或者你只是想遍历所有匹配项, 请继续按你想要搜索的方向键 (C-s 或 C-r). 如果你想反转方向, 只需按另一个方向键, Emacs 就会切换方向. 如果你到达了匹配项的末尾–或者如果你的搜索方向上没有匹配项–你可以通过再次按方向键从缓冲区的开头或结尾继续搜索 (取决于你的搜索方向). 微缓冲区会告诉你它是否绕回了搜索. Isearch 还会通过用红色35高亮显示失败的匹配项来告诉你搜索字符串的哪一部分未能匹配以及哪一部分匹配了.
    1. 大小写折叠 (Case folding)

      默认情况下, Isearches 不区分大小写; 小写搜索会匹配大写和混合大小写. 然而, 当你在搜索中使用一个或多个大写字母时, Emacs 会自动切换到区分大小写的搜索. 这称为大小写折叠 (case folding). 这是 Emacs 另一个奇怪的特性, 其他任何人都不会想到去实现它. 你可能正在寻找一个大写字符串, 但你的搜索字符串不必是大写的. 如果你确实需要区分大小写, 你所要做的就是拼出大写或混合大小写的名称, Emacs 只会查找文字匹配. 当然, 如果你只查找小写匹配项而不查找大写或混合大小写匹配项, 那么你别无选择, 只能禁用大小写折叠或使用 Isearch 的切换开关来临时启用区分大小写.

      如果你愿意, 你可以完全禁用大小写折叠: M-x customize-option case-fold-search RET

      一旦你开始使用 Isearch, 你会更想使用它的历史记录 (正式称为搜索环 (search ring)) 功能:

      Isearch Key Binding Purpose
      M-n 移动到搜索历史中的下一个条目
      M-p 移动到搜索历史中的上一个条目
      C-M-i 针对上一个搜索环的" TAB" 补全搜索字符串
      C-s C-s 针对上一个搜索字符串开始 Isearch
      C-r C-r 针对上一个搜索字符串开始向后 Isearch

      前两个现在应该是不言自明的了–因为它们在所有 Emacs 补全中普遍可用–但第三个值得仔细看看.

      1. Emacs 中的" TAB" 补全

        在 Emacs 中, C-M-i 是另一种" TAB" 补全机制, 与你在 M-x 提示符下按 TAB 时看到的机制并无不同. 在支持它的模式中–别忘了, 当你运行 Isearch 时, 你实际上是在与一个模式交互–该命令通常绑定到 complete-symbol, 这是一个通用的补全机制, 它会查看光标处的文本并尝试根据一组已知的补全项对其进行补全. 在 Isearch 的情况下, 按 C-M-i 也会触发补全引擎–但这是一个针对 Isearch 构建的, 考虑到其特殊性的不同引擎–但这次它会将你的 Isearch 搜索字符串与你的搜索历史进行比较. 试试看.

        搜索光标处的字符串是如此常见的操作, 以至于有专门的命令来帮助你做到这一点:

        Isearch Key Binding Purpose
        C-w 将光标处的词添加到搜索字符串
        C-M-y 将光标处的字符添加到搜索字符串
        M-s C-e 将光标处行尾的内容添加到搜索字符串
        C-y 从剪贴板粘贴 (yank) 到搜索字符串

        你经常会发现自己在一个你想搜索的词上, 为了省去手动输入的麻烦, 你只需键入 C-w. 重复调用会将后续的词添加到搜索字符串中. 我发现 C-M-y (一次添加一个字符) 对大多数人来说用处不大, 但如果你编辑大量带有外文字符的文本, 你应该试试.

        Isearch 是一种包容性搜索, 通常会倾向于谨慎行事, 匹配那些更传统, 更严格的搜索不会匹配的内容. 你可以使用其切换开关来控制 Isearch 的行为:

        Isearch Key Binding Purpose
        M-s c 切换大小写敏感性
        M-s r 切换正则表达式模式
        M-s w 切换单词模式
        M-s _ 切换符号模式
        M-s <SPC> 切换宽松的空白匹配
        M-s ' 切换字符折叠

        每个切换命令仅影响当前的 Isearch, 并且不会持久存在.

        大小写敏感性切换 (M-s c) 只是打开严格的大小写敏感匹配–适用于当你默认启用了大小写折叠但偶尔需要严格区分大小写搜索的情况.

        使用 M-s r 切换正则表达式模式类似于使用 C-M-s 或 C-M-r 激活正则表达式 Isearch, 反之亦然.

        单词和符号切换 (M-s w 和 M-s _) 会改变 Isearch, 使诸如 . 和 - 之类的单词和符号分隔符可以自由匹配其他分隔符.

        例如, 考虑一个包含以下文本的缓冲区:

        this-is-a-hyphenated-string
        

        仅使用 C-s 搜索 hyphenated string 不会产生匹配, 但如果你使用 C-s 重新运行搜索, 然后使用 M-s w 切换单词搜索模式, 它就会匹配. 单词搜索在编程语言中特别实用, 当你想匹配由 (可能未知的) 单词分隔字符分隔的两个连续单词时, 例如这个 C 示例:

        mystruct->foo = 42;
        

        如果你用 M-s w 切换单词搜索, 搜索 mystruct foo 会匹配上面的元素访问.

        Isearch 可以用 M-s ' 折叠字符. 这很巧妙: 当你搜索 a 时, Emacs 会尝试匹配像 á 或 å 这样的字符; 它适用于各种字符. 你可以通过自定义 search-default-mode 来默认启用它.

        我介绍过的一些切换开关和命令非常常用, 以至于它们有自己的全局按键绑定:

        Key Binding Purpose
        M-s w 向前搜索单词
        M-s _ 向前搜索符号
        M-s . 向前搜索光标处的符号
        M-s M-. 向前搜索光标处的事物 (Emacs 28)

        你应该能认出前两个, 因为它们与你在 Isearch 内部可用的按键绑定相同. M-s . 仅作为全局按键绑定可用. 它会开始向前搜索光标处的符号. 符号的定义取决于模式, 但 Emacs 通常足够聪明, 能够推断出符号的开始和结束位置.

        类似地, M-s M-. 会查找光标处的事物 (thing at point). 那个事物是什么是上下文相关的, 并由可自定义的选项 isearch-forward-thing-at-point 控制. 默认情况下, 它会按顺序检查区域选择, URL, 符号或 S-表达式. 这使其成为一个方便的" 一刀切" 工具, 可以获取你最可能感兴趣的事物.

        如果你想在活动区域内进行 Isearch, 那么你很可能也想将其用作 isearch 查询. 其他选项也是如此.

    2. Thing at Point

      在 Emacs 中, thing at point 是一个可扩展的系统, 用于提取光标周围" 有趣的" 文本. 这是一个包作者可以接入和扩展的系统, 并且在 Emacs 的许多部分都有使用.

      学习 Isearch: 它本身就是一个强大的搜索工具, 但它也让你通过搜索靠近你想要去的地方的词语来快速在缓冲区中移动. 传统的文本编辑器和 IDE 在传统的" 查找文本" UI 上附加了太多的修饰和复杂性. 在 Emacs 中, Isearch 几乎消除了视觉混乱和上下文切换, 因此你可以保持你的节奏.

  2. Occur: 打印并编辑匹配表达式的行

    image-page-162.png

    Figure 7: Occur 模式缓冲区, 显示了在当前文件中搜索 " foo" 的结果.

    Occur 模式是 Emacs 内置的一个类似 grep 的实用程序. 与 grep 不同, 它的功能要少得多, 并且默认情况下仅对当前缓冲区进行操作. M-x occur 之所以出色, 在于它的速度以及它随 Emacs 一起提供, 因此你不必调用外部进程. Occur 还会保留其匹配结果中的语法高亮.

    Isearch 会逐步引导你浏览缓冲区中的每个匹配项, 而 occur 则会创建一个名为 Occur 的新缓冲区, 其中包含所有匹配结果.

    你可以在全局和 Isearch 内部激活 occur:

    Key Binding Purpose
    M-s o Occur 模式
    M-s o 在 Isearch 内部激活当前搜索字符串的 occur

    与 Isearch 不同, 系统会要求你输入一个正则表达式进行搜索. Occur 模式会搜索与正则表达式匹配的行, 并在一个单独的缓冲区中显示结果. 有时, 你可能需要上下文行–匹配行本身之前和之后的行–你可以通过自定义变量 list-matching-lines-default-context-lines 来启用它们.

    Occur 模式使用超链接, 当你用鼠标左键单击它或在键盘上按 RET 时, 会跳转到匹配的行. Occur 缓冲区中使用的 occur 模式有许多你可以使用的按键:

    Occur Key Binding Purpose
    M-n, M-p 跳转到下一个和上一个匹配项
    <, > 跳转到缓冲区的开头和结尾
    g 还原缓冲区, 刷新搜索结果
    q 退出 occur 模式
    e 切换到 occur 编辑模式
    C-c C-c 退出 occur 编辑模式并应用更改

    这些按键仅在 occur 缓冲区本身中可用.

    在 Emacs 中, 按键 g 会还原缓冲区. 当你这样做时会发生什么取决于模式, 但刷新原始内容是 Emacs 的一个常见约定. 在本例中, 它会使用相同的正则表达式在缓冲区中重新运行搜索. g 命令值得记住, 因为它是 Emacs 中如此常见的约定.

    按键 e 会将 occur 缓冲区切换到可编辑状态. 这是一个令人难以置信的强大编辑结构, 允许你在 occur 缓冲区中内联编辑文本, 然后通过键入 C-c C-c 将更改提交到原始源行.

    occur 模式的主要优点是你会得到一个包含结果的第二个缓冲区, 通常在原始缓冲区旁边的第二个窗口中, 并且可以一目了然地查看匹配项并能够在匹配行之间跳转. 然而, 这需要你不断切换到另一个窗口来选择新的匹配项; 这不仅乏味, 而且会打断你的节奏. 为了保持节奏, 我建议你学习这两个命令:

    Key Binding Purpose
    M-g M-n 跳转到下一个" 错误"
    M-g M-p 跳转到上一个" 错误"

    目的列显示的是" 错误" , 但这是因为命令名称是 M-x next-error 和 M-x previous-error. 实际上, 它们是通用命令. 当你运行 M-x occur (或 Emacs 中的其他专门命令, 如 M-x compile 或 M-x grep) 时, Emacs 会记住这一点, 并使 M-g M-n 和 M-g M-p 在该匹配列表中上下移动. 这些命令的妙处在于你只需要记住这两个, 它们就可以与你上次执行的 occur, compile 或 grep 搜索一起使用.

    1. Multi-Occur

      你可以通过 multi-occur 在多个缓冲区上使用 occur 模式. 命令 M-x multi-occur-in-matching-buffers 接受一个要匹配的缓冲区的正则表达式–例如 \.py$ 来搜索所有 Python 缓冲区–但其他方面与 M-x occur 的工作方式相同. 还有一个 M-x multi-occur, 你可以在其中显式选择要搜索的缓冲区–这也很有用, 但使用起来较慢, 因为你必须手动选择要运行 occur 的每个缓冲区.

      通过 multi-occur, 你可以使用我之前解释的大多数命令: 这意味着你可以在匹配的缓冲区之间进行编辑 (使用 e). 现在将其与 Emacs 的键盘宏或搜索和替换结合起来, 你就拥有了一个非常强大的工具供你使用.

  3. Imenu: 跳转到定义

    Imenu 是一个通用的索引框架, 用于跳转到缓冲区中感兴趣的点. 主模式作者会编写一段 elisp 代码, 生成一个兴趣点列表–它们的名称以及它们在缓冲区中的位置–因此当你使用 M-x imenu 调用 imenu 时, 你可以跳转到其中任何一个.

    大多数 (但并非所有) 主模式都支持 imenu. 对于编程模式, 最明显的兴趣点是函数和类定义之类的东西; 其他模式也可能使用它们, 例如邮件程序或像 Markdown 或 reStructuredText 这样的结构化文本模式.

    Imenu 是你工具箱中用于中长距离移动的另一个工具. 我发现当我不确定某物在缓冲区中的确切位置时最常使用它; 对于跳转到屏幕上看到的东西, Isearch 或基本移动命令可能更好 (也更快).

    奇怪的是, imenu 根本没有绑定到任何按键. 要使用它, 你必须键入 M-x imenu. 这很不幸, 因为我认为这在历史上阻碍了它的采用, 因为它没有绑定到任何已知或可访问的按键. 正如你在自己探索 Emacs 时会发现的那样, 这种情况很常见–事实上非常常见, 以至于即使是 20 年的老用户仍然会在 Emacs 中发现他们以前从未听说过的新东西.

    我将 imenu 绑定到 M-i. 然而, 该键已被占用. 现有的命令 M-x tab-to-tab-stop 会插入空格或制表符以到达下一个制表位, 这是一个可以追溯到–并且从那以后就再也没用过–打字机时代的概​​念. 就我个人而言, 我不需要这样的东西, 当然也不需要这样一个易于访问的按键.

    要将 Imenu 绑定到 M-i, 请将此添加到你的 init file:

    (global-set-key (kbd "M-i") 'imenu)
    

    与 Emacs 的大多数补全提示一样, Imenu 开箱即用仅支持 TAB 风格的补全. 我建议你阅读下一章并使用 Helm 的 Imenu 支持.

  4. Helm: 增量补全和选择

    helm.png

    Figure 8: Helm 界面, 显示了在 Apropos 搜索 " ^forward" 后的补全结果.

    Helm 是一个了不起的包. 它是一个通用的" 边输边过滤" 补全框架; 也就是说, 你开始输入, Helm 会自动过滤并显示匹配项–与 Isearch 的实时增量搜索并无不同.

    Helm 之所以如此出色, 是因为它开箱即用就带有很多补全命令. 我的个人建议是按照下面的安装说明操作并立即开始使用 Helm. Helm 与 Emacs 通常低调的补全机制截然不同–特别是如果你已经有一个适合你的工作流程–但 Helm 广泛的补全源选择及其" 边输边过滤" 功能显然优于 Emacs 自己的基于 TAB 的补全机制 (适用于许多但并非所有任务).

    1. 如何安装

      Helm 是一个第三方软件包, 不随 Emacs 一起提供. 安装它的方法有很多, 但这是最简单的方法:

      1. 确保你已遵循" 包管理器" 中的说明.
      2. 运行 M-x package-install, 然后输入 helm 并按 RET.
      3. 重新启动 Emacs.

      所有 Helm 命令都共享前缀键 C-x c. 我不能说我非常喜欢那个前缀键, 因为很多跟在它后面的键都让它变成了手指的扭曲练习. 它也与 C-x C-c–退出 Emacs 的命令–非常接近.

      Prefix Key Binding Purpose
      C-x c 所有 Helm 补全命令的前缀键
    2. 探索 Helm

      在 Emacs 中发现事物的诀窍是向 Emacs 提出正确的问题. 在本例中, 正确的问题是: Helm 提供了哪些命令给我? 以及 Helm 有任何按键绑定吗?

      • 使用 apropos: 正如我在 Apropos 中谈到的, apropos 会列出与你给定的模式匹配的所有 elisp 符号–变量, 命令, elisp 函数等等. 在本例中, 请求 M-x apropos-command (使用 C-h a) 来显示所有与 ^helm- 匹配的命令会是一个不错的起点. 同样, M-x apropos-variable 也会对变量执行相同的操作. Elisp 没有命名空间, 因此包作者会用包的名称作为其命令的前缀. 由于包名是 Helm, 因此使用 apropos 查找以 helm- 开头的命令是合理的–或者更准确地说, 使用正则表达式 ^helm-. 巧合的是, Helm 有自己的 apropos 补全引擎: M-x helm-apropos. 它会补全命令和变量及函数–确保你只查看 Commands 标题下的内容, 因为 Functions 是不用于一般用途的内部 elisp 函数.
      • 描述前缀键: 我之前提到 Helm 有自己的前缀键 C-x c. 在" 描述系统" 中, 你可以通过在 C-h 后跟前缀键来让 Emacs 列出前缀键中的所有按键绑定: C-x c C-h.

      这两种方法会产生略微不同的答案–因为你实际上问的是两个不同的问题–所以你应该两者都做.

      最后:

      • M-x apropos-command (C-h a) 不限于绑定到前缀键的命令, 它会很乐意显示未绑定的命令 (如果你的搜索模式太通用, 甚至会显示完全不相关的命令)–但这仍然是发现隐藏命令的好方法, 无论是否绑定.
      • 用 C-h 描述前缀键只会显示绑定到该前缀键的命令. 有时, 你会发现一些命令虽然绑定到该前缀键, 但与该前缀键的其他命令无关.
    3. Helm 绑定

      由于 Helm 命令数量众多, 我只列出我认为最重要的那些. 我鼓励你遵循" 探索 Helm" 中的建议, 自己探索 Emacs 和 Helm.

      在此之前, 我应该谈谈 Helm 的操作 (actions). 在 Helm 中, 你还可以对匹配项执行操作; 可用的操作完全取决于你正在执行的补全.

      Helm 有自己的一套你需要学习的按键:

      Helm Key Binding Purpose
      RET 主要操作
      C-e 次要操作
      C-j 第三操作
      TAB 切换到操作选择器
      C-n, C-p 下一个和上一个候选项
      M-<, M-> 补全列表的开头和结尾

      RET 是你希望对所选候选项执行的主要 (也是最常见的) 操作. 通常, 它会跳转到, 打开或显示该候选项. 次要操作 (如果存在) 绑定到 C-e, 你可能还记得这是一个跳转到行尾的基本移动命令; 该命令在 Helm 中仍然会这样做, 但仅当你的光标不在行尾时–如果它在行尾, 它就会执行次要操作.

      1. 退出 Helm

        要快速退出 Helm, 请按 C-g, 这是通用的" 退出任何东西" 键.

        TAB 键会切换到操作选择器并列出所选候选项的所有可用操作. 与 Helm 补全界面类似, 操作界面也是" 边输边过滤" 的.

        以下是一些更有趣的 Helm 补全引擎:

        Key Binding Purpose
        C-x c b 恢复上一个 Helm 命令
        C-x c / 在活动缓冲区的当前目录上调用命令行实用程序 find
        C-x c a 补全 M-x apropos 结果
        C-x c m man page 程序的补全引擎
        C-x c i 列出来自 M-x imenu 或 Semantic 的补全项
        C-x c r 交互式正则表达式构建器
        C-x c h r 在 M-x info 中搜索 Emacs 主题
        C-x c M-x 列出来自 M-x 的补全项
        C-x c M-s o 使用 Helm 匹配 M-x occur 模式
        C-x c C-c g 显示来自 Google Suggest 的匹配项

        有些 Helm 命令的按键绑定简直复杂到家了, 比如用 C-x c C-c g 来显示 Google Suggest 的匹配项. 即使以 Emacs 的标准来看, 它们也很晦涩.

        学习 Helm 会极大地改善你的 Emacs 体验. 它带有强大的模糊搜索和大量现成的补全机制. 它还有自己蓬勃发展的第三方包生态系统.

        关于 Emacs 的有趣之处在于你会从像 Google Suggest 补全机制这样的东西中获得意想不到的生产力提升. 当你可以在 Emacs 中完成并在输入时从 Google 获取建议时, 为什么要切换到 Web 浏览器并在浏览器中搜索呢?

  5. IDO: 交互式执行操作

    ido.png

    Figure 9: IDO 模式下的文件查找界面.

    正如我在" 缓冲区切换替代方案" 中谈到的, IDO 模式是一个强大的微缓冲区补全引擎. Helm 和 IDO 在目的上有所重叠, 有些人更喜欢使用其中一个而排除另一个; 大多数人两者都使用, 但用于不同的目的:

    • IDO 是一个无干扰, 目标明确的搜索: 与 Helm 不同, IDO 不使用单独的缓冲区 (和窗口) 来显示补全匹配项. 相反, 它会在你的微缓冲区本身内联补全. 对于像缓冲区切换和文件查找这样的事情, 这更可取, 因为你大致知道你要去哪里以及要查找什么. 我就是这样使用 IDO 模式的, 我认为这是一个很好的起点; 对于几乎所有人来说, 它肯定比默认的基于 TAB 的补全机制更好.

    然而, 当你不太确定要查找什么时, IDO 就会失效; 在微缓冲区中逐步浏览匹配项是很乏味的, 而你想要的只是一个匹配你查询的所有内容的概览.

    • Helm 用于深度搜索和补全: Helm 会打开一个临时缓冲区和窗口来显示匹配项, 这极大地增加了视觉混乱和干扰–如果你不确定要查找什么, 这本身并非坏事. 但是如果你知道自己在做什么–例如你在缓冲区 foobar.txt 中并且想打开 widgets.c–IDO 是更好的选择, 因为你可以无缝切换缓冲区而不会受到视觉开销的影响. 的确, 如果你是一个触摸打字员, 你很快就会达到一个境界, 即便不看微缓冲区也能在缓冲区之间跳转, 因为你训练有素的直觉会告诉你, 如果你键入 C-x b wc, IDO 会灵活匹配到 widgets.c. 当你不太清楚要查找什么, 或者需要对类似匹配项进行额外的上下文感知时, Helm 会表现出色.

    我建议你为文件和目录查找以及缓冲区切换启用 IDO 模式, 方法是将以下几行添加到你的 init file 中:

    (ido-mode 1)
    (setq ido-everywhere t)
    (setq ido-enable-flex-matching t)
    

    现在, 当你查找文件或目录 (C-x C-f 和 C-x d) 或切换缓冲区 (使用 C-x b) 时, 你将使用 IDO 远优于此的补全机制. 当然, 如果你愿意, 也可以自由使用 Helm.

    我建议你学习几个 IDO 广泛的按键绑定.

    1. 文件和目录切换

      这些按键仅在你运行需要选择文件或目录的命令时可用. 这包括 C-x C-f 和 C-x d,但也包括像使用 C-x C-s 保存文件这样的事情:

      Key Binding Purpose
      C-s 和 C-r 移动到下一个和上一个匹配项
      TAB 传统的非 IDO TAB 补全
      RET 打开选定的匹配项
      C-d 在当前目录中打开 M-x dired 缓冲区
      // 转到根目录 /
      ~/ 转到主目录 ~
      Backspace 删除一个字符或向上移动一个目录
    2. 缓冲区切换

      缓冲区切换通常只在你键入 C-x b 时遇到:

      Key Binding Purpose
      C-s 和 C-r 移动到下一个和上一个匹配项
      TAB 传统的非 IDO TAB 补全
      RET 将活动缓冲区切换到选定的匹配项
    3. 进一步阅读

      我建议你阅读我的文章<Ido 模式简介>(Introduction to Ido Mode)36, 以获取有关如何使用 IDO 模式的更深入信息.

  6. Grep: 搜索文件系统

    在 Emacs 中搜索已打开的文件是可以的, 但更多时候你希望搜索未在 Emacs 中打开的文件, 而命令行实用程序 grep 是完成此任务的好方法.

    1. grep in Microsoft Windows

      Windows 中没有类似 grep 的程序–内置的 findstr 虽然强大, 但 Emacs 开箱即用并不支持–所以我建议你安装 GNU Coreutils 的交叉编译 Windows 版本37. 它们在 Windows 中运行良好, 并为你提供了一个合理的 Linux 命令行模拟环境.

    2. ack, ag 和 ripgrep

      虽然功能强大, 但你可能更喜欢 grep 以外的其他工具, 例如 ripgrep. 你可以在支持它们的包管理器中找到第三方包.

      Emacs 支持大量的 grep 和 grep 衍生命令. 默认情况下没有一个绑定到按键上, 因此你需要直接使用 M-x 调用这些命令, 稍后再将你经常使用的命令绑定到按键上:

      Command Purpose
      M-x grep 提示要传递给 grep 的参数
      M-x grep-find 提示要传递给 grep 和 find 的参数
      M-x lgrep 提示要使用 grep 搜索的查询和 glob 模式
      M-x rgrep 提示查询和 glob 模式, 然后使用 grep 和 find 递归搜索
      M-x rzgrep 类似 M-x rgrep, 但搜索压缩的 gzip 文件

      grep 命令分为两类:

      • 低级命令, 如 M-x grep 和 M-x grep-find. 它们为你提供一个建议的 grep 命令字符串, 你只需要添加搜索模式和任何其他需要的选项. 我不经常使用它们. 我通常想在一组文件中搜索一个模式, 而这些命令对于这个目的来说太低级了. 偶尔, 我想用特定的选项调用 grep, 在这种情况下, 我别无选择, 只能使用 M-x grep 或 M-x grep-find.
      • 高级命令, 如 M-x lgrep, M-x rgrep 和 M-x rzgrep. 它们完全隐藏了命令字符串, 而是要求你提供要搜索的文件和要匹配的搜索字符串. Emacs 还会根据你当前缓冲区的文件类型巧妙地建议一个文件类型. Emacs 还会查看你光标所在的当前符号, 并询问你是否要搜索它. 这很方便, 因为你经常会发现自己在一个想要搜索的标识符或单词上, 或者在它附近. 在这种情况下, 你可以键入 M-x rgrep 并按两次 RET 来接受默认值.
    3. Grep Guesswork

      高级命令–特别是 rgrep–在调用 find 时会进行大量巧妙的幕后猜测. 首先, Emacs 运行在各种平台上, 并且必须在所有平台上保持一致. 并非所有平台都带有 xargs, 因此 Emacs 会检查这一点并改用 find 自己的 -exec 开关. 字符串中的引号和转义字符在不同平台和 shell 上有所不同–Emacs 需要与所有这些都兼容.

      1. For Windows Users

        在 Windows 上, 已经有一个名为 find 的程序. 要覆盖 Emacs 中的默认选择, 你应该将此添加到你的 init file 中, 确保将 C:\\gnuwin32\\bin\\ 更改为 GNU find 的位置, 然后重新启动 Emacs:

        (setenv "PATH" (concat "C:\\gnuwin32\\bin\\"
                               path-separator
                               (getenv "PATH")))
        

        Emacs 的另一个利器是能够自动将否定匹配传递给 find. 例如, 你不希望搜索像 .git 这样的源代码控制目录或会产生错误肯定结果的垃圾文件. 许多人使用像 ack 或 rg 这样的工具, 因为它们可以轻松地包含或排除文件模式–但幸运的是, Emacs 自动处理了这种繁琐的工作. 我鼓励你使用 Customize 浏览 grep 类别并根据自己的喜好进行配置: M-x customize-group RET grep RET

    4. 使用 Grep 界面
      Key Binding Purpose
      M-g M-n 跳转到下一个匹配项
      M-g M-p 跳转到上一个匹配项

      正如我在谈论 M-x occur 时提到的, 你可以重用跳转命令. 和以前一样, 它们是全局的, 并且可以跨缓冲区边界工作. 它们值得了解, 因为你可以在 grep 缓冲区中的匹配项之间快速轻松地跳转. Emacs 会打开文件并跳转到正确的行 (如果它们尚未打开), 如果它们已经打开, Emacs 只会切换到已打开的文件.

      1. Grepping in Emacs

        与 Emacs 中的所有东西一样, 这也与模式有关, grep 的主模式名为–你猜对了–grep-mode. 因为缓冲区是 Emacs 的通用数据结构, 它所要做的就是将 grep 的输出通过管道传输到一个名为 grep 的缓冲区, 然后调用 grep-mode. 激活后, grep-mode 会高亮显示匹配项并将其超链接到行号和文件名, 以便你可以四处跳转. 如果你仔细观察, grep 缓冲区中的输出看起来与 grep 本身的输出完全相同! 你可以通过打开一个空缓冲区 (C-x b 然后选择一个未使用的名称) 并输入类似以下内容来测试这一点: myfile.txt:10:This does not exist! 然后键入 M-x grep-mode, 看着 Emacs 高亮显示匹配项, 就好像它是 grep 的真实输出一样. 这是 Emacs 中的一个常见模式: 重用命令的原始输出虽然技术含量不高, 但很有效!

4.0.10. 其他移动命令

这些移动命令对于大多数 Emacs 用户来说日常用处有限. 它们有其用武之地, 一旦你熟悉了本章中所有其他命令, 有些命令值得融入你的工作流程.

Key Binding Purpose
M-r 将光标重新定位到左上, 左中或左下
C-l 将光标重新居中到缓冲区的中间, 顶部或底部
C-M-l 重新定位注释或定义, 使其在缓冲区中可见
C-x C-n 设置目标列, 即光标的水平位置
C-u C-x C-n 重置目标列, 即光标的水平位置
M-g M-g 转到行
M-g TAB 转到列
M-g c 转到字符位置

M-r 和 C-l 功能相似. M-r 会首先将你的光标移动到行首, 然后在缓冲区的顶部, 中间和底部之间交替. C-l 与此类似, 只是它会滚动你的窗口, 使光标所在的行重新居中到顶部, 中间或底部. C-M-l 与此类似, 只是它会智能地尝试重新定位窗口, 使注释或定义点所在的行在缓冲区中可见. 换句话说, 它会尝试将内容滚动到视图中.

我一直使用 C-l. 我用它来重新居中我所在的行, 这样我就可以看到该行上方或下方的更多缓冲区内容. 我鼓励你尝试一下 M-r 和 C-l.

目标列命令 C-x C-n 对大多数人来说用处有限. 当你向上或向下移动一行时, Emacs 会尝试保持你的水平位置. 如果你设置了目标列, Emacs 将不会这样做, 而是使你的光标的水平位置与目标列匹配. 因此, 如果你将目标列设置为 10–通过将光标放在第 10 个字符上–Emacs 会尝试 (如果该行足够长!) 确保你的光标始终位于第 10 个字符处. 要禁用目标列, 请键入 C-u C-x C-n.

跳转到某一行是你经常想做的事情. 但是由于 Emacs 的交互式编译模式和对 grep 之类东西的内置支持, 你不必像使用更简单的编辑器那样频繁地跳转到显式行. 绑定到 M-g M-g 的命令完全符合你的预期: 它会要求你输入要跳转到的行. 你也可以给它一个前缀参数, 例如 M-5 M-5 M-g M-g, 跳转到第 55 行–并确保使用 M- 数字参数来保持你的节奏.

命令 M-g TAB 的作用与此相同, 只是它会跳转到特定的列位置. M-g c 会跳转到缓冲区中从开头算起的绝对位置.

4.0.11. 结论

正如前面的子章节所示, 在 Emacs 中有多种移动方式. Emacs 经常因其是一个操作系统但却是一个糟糕的文本编辑器而受到指责, 但这与事实相去甚远; Emacs 是一个高度复杂的文本编辑器, 其功能轻松匹敌 Vim–即使这两个编辑器在功能上和方法上都有所不同. Emacs 的修饰键是一种瞬态模态 (transient modality) 的形式. Emacs 明显是模态的, 因为你的命令会随着修饰键而改变, 并且会一直保持这种状态, 直到你释放修饰键. 最能带来改变的一件事是重新映射 Caps Lock 为 Control: 我离不开这个, 即使在 Emacs 之外也是如此. 如果你是一个触摸打字员, control 键的位置会很别扭.

Emacs 的命令也有很多对称性, 特别是基本的移动命令. 并非所有按键绑定都有意义, 也有一些愚蠢的疏忽, 比如没有将 M-x imenu 绑定到一个键上.

如果你是 Emacs 新手, 我建议你继续使用箭头键. 你可以一次采用一个 Emacs 的移动命令. 最终, 你会慢慢接受某些 Emacs 的特性, 并且很快就会意识到将右手从本位行移开来使用箭头键会降低你的速度.

我的下一个建议是进行实验. 不断参考本书, 直到形成肌肉记忆; 不断尝试不同的组合, 训练你的大脑识别模式. 这完全关乎肌肉记忆和模式匹配–知道如果你执行这个命令, 你会得到那个结果. 没有人能在一夜之间掌握 Emacs, Emacs 的精通本身就是一个伪命题; 它对一百个人意味着一百种不同的东西.

在整个章节中, 我向你展示了如何使用 Emacs 的内部文档查找内容–特别是 apropos, C-h 和 describe system–这比任何其他东西都能帮助你" 掌握" Emacs. 忘记一个命令的名称或它绑定的按键是无关紧要的, 只要你知道如何在 Emacs 中查找答案.

我的最后建议是关于实验. 每当你做某件事, 并且认为你可以用更智能或更有效的方式来做时–再读一遍这本书, 或者在互联网上搜索建议. 我自己的许多技巧和工作流程都是在意识到我一直面临的特定问题有比幼稚的手动方式更好的解决方案后自然形成的.

5. 第五章 编辑理论

" 无数只猴子在 GNU Emacs 上打字, 也永远写不出一个好程序." – Linus Torvalds, <Linux Kernel Coding Style Documentation>

在 Emacs 中进行编辑可能比学习如何在 Emacs 中高效移动更容易. 大多数日常编辑是编写或删除文本, 并穿插一些专门的命令. 尽管如此, 在 Emacs 中, 即使是像删除文本或使用 kill ring (剪贴板) 这样普通的任务也经过了高度优化.

我认为首先掌握移动更重要, 因为这意味着你学会了如何切换缓冲区并有效地使用 Emacs 的窗口系统. 这就是为什么直到现在, 本书的三分之二内容都还没有谈论编辑文本. 一旦你能够轻松地打开和保存文件并在 Emacs 中四处移动而不会迷失方向–那么你就可以开始学习更高级的编辑概念了.

如果你正在阅读本章并且仍在使用导航键–箭头键, page up 和 page down 等等–那也没关系. 你会发现体验有些脱节, 因为 Emacs 移动键之所以如此高效, 很大程度上在于它们与文本编辑对应部分的近乎和谐的关系.

本章将介绍如何编辑文本; 包括搜索和替换等传统主要功能; 如何使用 kill ring 或剪贴板; 如何使用文本宏; 以及如何使用文本转换工具.

5.0.1. Killing 和 Yanking 文本

其他文本编辑器只是剪切 (cut) 文本, 而在 Emacs 中你是杀死 (kill) 它. 正如我在" Killing, Yanking and CUA" 中谈到的, 这个术语很奇怪, 并且早于大多数图形用户界面.

  1. 发现 Kill 命令

    使用 Emacs 的 apropos 功能查找此处未列出的其他 kill 命令.

    Emacs 的 kill 命令使用与移动命令相同的语法单位 (syntactic unit) 概念. 其中一些还共享修饰符对称性, 从而可以轻松地在 kill 命令之间切换.

    Key Binding Purpose
    C-d 删除字符
    <backspace> 删除上一个字符
    M-d, C-<backspace> Kill 词
    C-k Kill 行的其余部分
    M-k Kill 句子
    C-M-k Kill S-表达式
    C-S-<backspace> Kill 当前行

    上表中比较突出的是 C-d, 它删除下一个字符; 以及 <backspace>, 它也做同样的事情, 只是向后删除. 所有其他命令是 kill 而不是 delete. 这个区别很重要: deleted 文本不会保留在你的 kill ring 中, 而 killed 文本会.

    1. 数字参数和负参数

      与移动命令类似, 你可以使用数字参数一次 kill 多个单位. 为了保持你的节奏, 请确保使用与你想要调用的 kill 命令的修饰符相同的数字修饰符. 如果你想用 C-M-k kill 3 个 S-表达式, 请键入 C-M-3 C-M-k. 负参数会反转方向, 就像移动命令一样. 不要搞错: 这比看起来更实用. 我经常在写完一些东西后才意识到我想把它移到别处, 或者干脆完全删除它. 考虑这个例子:

      s = make_upper_case("hello, world!")
      

      在 C-M– C-M-k 之后:

      s = make_upper_case()
      

      Emacs 也有许多通用的 kill 和 yank 命令. 它们值得记住, 因为你会经常使用它们:

      Key Binding Kill Ring Purpose Clipboard
      C-w Kill 活动区域 cut
      M-w 复制到 kill ring copy
      C-M-w 追加 kill  
      C-y Yank 上一个 kill paste
      M-y 循环浏览 kill ring, 替换 yanked 文本.  
        可能显示 kill ring 历史 (Emacs 28)  

5.0.2. Killing 与 Deleting 的区别

killing 和 deleting 之间的区别让许多 Emacs 新用户感到困惑. 在大多数编辑器中, 剪贴板命令–仅对选定文本起作用–和删除文本的命令之间有明确的界限.

在 Emacs 中, 除了 C-d 和 <backspace> 等少数例外, 所有命令都会将文本直接 kill 到你的 kill ring 中. 如果你是 Emacs 新手, 这会让你感到困惑, 甚至可能激怒你.

没有其他编辑器会摆弄你的剪贴板内容, 除非你明确告诉它这样做–但 Emacs 会, 一旦你习惯了它, 这就是一个很棒的功能.

Emacs 的 kill 命令最好用五条简单的规则来概括:

  • 连续 kills 追加: 所有 kill 命令都会追加到 kill ring–也就是说它们会追加到 kill ring 中的文本–当且仅当上一个命令也是一个 kill 命令. 如果你通过移动, 书写或运行命令来打破这个循环, 下一个 kill 命令将在 kill ring 中创建一个新条目. 例如, 如果你键入 M-d M-d M-d–连续 kill 三个词–你的 kill ring 在你下次 yank 文本时将包含你 kill 的那三个词. 如果你键入 M-d M-d M-d, 然后用 C-n 移动到下一行并 kill 另外三个词. 你从 kill ring 中 yank 的是你最后 kill 的三个词, 而不是全部六个! 移动命令打破了这个循环. 这需要一些时间来适应. 但这是一种更智能的工作方式, 因为你不必先选择文本.
  • kill ring 可以容纳许多项目: 与 undo ring 非常相似, 你在 kill ring 中几乎不会丢失信息. 如果你 kill 了某些东西, 然后稍后用另一个 kill 替换了你的第一个 kill 条目, 你并没有丢失你的第一个 kill. 它可以轻松恢复, 事实上, kill ring 经常被用作重写文本时片段的临时和辅助" 存储" . kill ring 确实有一个最大大小–或者更确切地说, 是它在悄悄丢弃最旧条目之前容纳的文本单元数量–你可以通过修改 kill-ring-max 来对其进行自定义. 不过, 我自己从未真正需要这样做, 因为默认值很慷慨.
  • kill ring 是全局的: 它在 Emacs 的所有缓冲区之间共享. kill ring 中的每个项目都随处可用.
  • Killing 也是 deleting: 当我开始我的 Emacs 之旅时, 我发现奇怪的是没有专门的 delete 命令. 但这是因为 kill ring 既是不需要文本的垃圾场, 也是有用文本的剪贴板. 很少有直接 delete 文本的命令–Emacs 很少会让你处于意外数据丢失的境地–所以这就是为什么所有 bounds 命令都将文本发送到 kill ring 的原因. 但这也没关系: kill ring 是有限的, 但比你可能关心的要大得多. 忘掉你的 kill ring 很珍贵的想法–它不是.
  • 标记是不必要的: 无论是文本还是散文, 你会发现你所做的大部分工作都涉及对语法单位的操作. 连续按三次 M-d 来 kill 三个词比先用 M-@ 标记它们然后再用 C-w kill 要快得多. 不过有两个例外:
    • 如果你想复制 (M-w) 该区域, 先标记然后复制会更快.
    • 如果你想 kill 或复制不符合多个语法单位的不规则形状区域.
  1. 追加到 kill ring

    有时, 你想将一个新的 kill 追加到 kill ring 中的现有 kill. 如果你想 kill 缓冲区的不同部分, 而这些部分不是一个连续的区域或一系列 kill 命令, 这种情况经常发生. 为此, 首先键入 C-M-w, Emacs 会在回显区告诉你, 如果下一个命令是 kill 命令, 它将追加到 kill ring.

  2. Killing Lines

    如果你想 kill 整行, 你应该使用 C-S-<backspace>–但该命令在终端中不起作用, 因为终端模拟器的技术限制无法实现.

    另一种方法, 也是我自己使用的方法, 是修改 C-w 的行为–kill 活动区域的命令–以便在区域未激活时 kill 光标所在的当前行. 如果你也想让你的 Emacs 这样做, 我建议你安装 whole-line-or-region 包: M-x package-install RET whole-line-or-region RET 类似地, 还有 C-k, 它会 kill 到行尾. 这种行为与大多数人的预期不同: C-k 不会 kill 行尾的换行符. 这是一个特性, 而不是一个 bug. 当你想在别处 yank 文本时, 很少需要换行符: 如果你这样做了, 你的 kill ring 中现在会有一个多余的换行符, 而你的缓冲区中会少一个. 相反, 如果光标在行尾, 则只会 kill 换行符. 因此, 连续按两次 C-k 会 kill 文本和换行符, 从而可以在你确实需要换行符时轻松地将两者都包含在同一个文本单元中.

    如果你愿意, 你可以通过自定义选项 kill-whole-line 来强制 C-k 永久 kill 换行符. 但我鼓励你在做出更改之前尝试一下默认设置.

5.0.3. Yanking 文本

在 Emacs 中, 如果你想从 kill ring 中粘贴 (paste) 文本, 你是 yank 它.

  1. 术语

    Paste 在 Emacs 中不是一个广泛使用的术语. 然而, 由于它在其他地方如此普遍–以及其他一些词语–Emacs 在你使用 apropos 时会将其视为 yank 的同义词.

    你需要了解的两个 yank 命令是:

    Key Binding Purpose Clipboard
    C-y Yank 上一个 kill paste
    M-y 循环浏览 kill ring, 替换 yanked 文本.  
      可能显示 kill ring 历史 (Emacs 28)  

    Yanking 的工作方式与你预期的一样: 它会将 kill ring 中的当前条目插入到活动缓冲区的光标处. 重复调用 yank 会插入相同的文本.

    正如我之前提到的, kill ring 是一个环 (ring), 就像 undo ring 一样, 它会记住以前的 kills, 以便你可以循环浏览它们.

    循环浏览 kill ring 很简单:

    1. 在你想让 yanked 文本出现的地方按 C-y.
    2. 在不执行任何其他命令的情况下–这包括四处移动和编辑文本–键入 M-y 以在 Emacs 的 kill ring 中向后移动.

    从 Emacs 28 开始, 你可以用 M-y 浏览 kill ring 并选择要 yank 的 kill ring 条目. 此功能仅在你没有刚刚用 C-y 调用 yank 的情况下可用–换句话说, M-y 的默认操作是显示 kill ring 历史记录.

5.0.4. Transposing 文本

转置文本 (Transposing text) 是将两个文本语法单位相互交换的行为. 乍一看, 你可能认为它们的用处有限; 但实际上它们解决了一个真正的问题, 如果你花时间和精力去掌握它们, 你不会后悔的. 在散文中切换词语的顺序, 或者函数参数的顺序, 都是常见的操作.

当你转置文本时, 你使用的语法单位与移动或 kill 文本的方式大致相同:

Key Binding Purpose
C-t 转置字符
M-t 转置单词
C-M-t 转置 S-表达式
C-x C-t 转置行
M-x transpose-paragraphs 转置段落
M-x transpose-sentences 转置句子

当你调用一个 transpose 命令时, Emacs 会首先查看光标的位置, 并根据你发出的确切 transpose 命令, 交换光标周围的两个语法单位.

在这种情况下, Emacs 如何定义语法单位有点复杂, 因为你的主模式决定了什么是语法单位.

负参数也有效; 数字参数也有效, 但方式与你预期不同. 当你给 transpose 命令一个数字参数时, 它会获取光标前面的第 N 个单位 (除非你也给它一个负参数, 在这种情况下则相反), 并将该单位与光标前面的那个单位交换.

  1. C-t: 字符转置

    转置字符会将光标左右两侧的字符交换:

    A█BC
    

    在 C-t 之后:

    BA█C
    

    注意光标向前移动了一个字符, 以便你可以重复调用 C-t 将字符" 拉" 到右边:

    BCA█
    

    此规则的一个重要例外是当你在行尾时. C-t 会交换光标左侧的两个字符:

    BCA█
    

    在 C-t 之后:

    BAC█
    

    这种不对称性是修复拼写错误的巧妙方法. 用 C-t 修复输错的字符可以节省时间, 因为它省去了删除两个字符并重新输入的麻烦.

  2. M-t: 单词转置

    用 M-t 转置两个词, 当这些词是纯文本时, 其工作方式与你预期的一样, 如下所示:

    Hello █World
    

    在 M-t 之后:

    World Hello█
    

    与用 C-t 转置字符类似, 光标会向前移动, 就好像你键入了 M-f (M-x forward-word) 一样, 这意味着你可以将一个词" 拉" 到右边.

    M-t 真正大放异彩的地方是当你用它处理源代码时. 在" 什么构成单词?" 中, M-f 和 M-b 移动命令会忽略你移动方向上的符号, 而 M-t 的行为也相同.

    考虑这个 Python 代码示例, 其中我们有一个字典 (键值哈希映射):

    names = {
      'Jerry':█ 'Seinfeld',
      'Cosmo': 'Kramer',
    }
    

    当光标在键和值之间时, 调用 M-t 简直是魔术:

    names = {
      'Seinfeld': 'Jerry',
      'Cosmo': 'Kramer',
    }
    

    如你所见, 键和值交换了位置, 但符号保持不变. 再重复一次, Emacs 会继续交换 Jerry 和 Cosmo; 再重复一次, 你会交换 Jerry 和 Kramer.

    1. 转置的实际工作原理

      M-t 命令与 M-x forward-word (M-f) 命令有内在联系. 简单地说, Emacs 会调用 M-f 两次: 一次带负参数, 以获取要转置的左侧单词; 再一次不带负参数, 以获取要转置的右侧单词. 实际情况要复杂一些, 但也差不了多少. 这也是一个很容易测试的理论: 从你的原始位置调用 M– M-f 和 M-f–确保在两次调用之间将光标移回原始位置–你会发现 M-t 将转置的单词的左右边缘.

      如果 Emacs 的单词移动行为之前让你觉得没有意义, 我希望现在它更有意义一些. 这并非所有人都喜欢, 但它在移动, kill 和转置方面是一致的.

      它也适用于散文:

      Hello,█ World!
      

      在 M-t 之后:

      World, Hello█!
      
  3. C-M-t: 转置 S-表达式

    你可以用 C-M-t 转置 S-表达式–平衡表达式–并且, 就像用 M-t 转置单词一样, 机制是相同的; 当转置函数找到左右边缘时, 同样的前后原则也适用.

    考虑以下 LISP 代码片段:

    (/ (+ 2 n)(* 4 n))
    

    对其调用 C-M-t 会交换这两个形式的位置:

    (/ (* 4 n) (+ 2 n))
    

    与之前的 M-t 类似, 概念是相同的, 但应用不同. 但是 C-M-t, 很像 M-x forward-sexp (C-M-f), 如果没有平衡表达式, 则扮演 M-x transpose-word 的角色:

    Hello,█ World!
    

    在 C-M-t 之后, 它变成:

    World, Hello█!
    

    但考虑一下如果我们将平衡表达式与一个词混合在一起会发生什么:

    Hello,█ (insert name here)!
    

    在 C-M-t 之后:

    (insert name here), Hello█!
    

    所以, C-M-t 仍然如你所料地工作. 其适用性在代码中也很明显:

    ages = {
      'Seinfeld':█ 34,
    }
    

    正如你所料, Emacs 正确地转置了内容:

    ages = {
      34: 'Seinfeld'█,
    }
    

    这种行为与 M-t 不同. 考虑与之前相同的场景, 但使用 M-t:

    ages = {
      '34': Seinfeld█,
    }
    

    结果确实不同. Emacs 确实交换了单词, 但保留了符号不变. 这与 Emacs 处理单词和单词边界的方式是一致的.

  4. 其他转置命令

    你也可以转置其他语法单位–行, 段落和句子–除了转置行之外, 我认为其余的很难证明立即学习是合理的. 段落和句子命令是未绑定的, 这使得它们更难使用–使用它们的唯一方法是通过 M-x 调用它们.

    然而, 用 C-x C-t 转置行确实有用. 我经常用它来重新排序基于换行符的列表, 我发现它在编程中也有其用武之地.

5.0.5. Filling 和 Commenting

  1. Filling

    如果你写大量文本, 你偶尔需要手动断开段落, 以免行超出特定长度. 你可以使用 Emacs 的 fill 功能来为你完成此操作, 无论是手动还是在你书写时自动进行. fill 命令不仅仅用于文本. 例如, 你也可以 fill 注释或文档字符串, 使它们符合 80 个字符的限制.

    Key Binding Purpose
    M-q 重新填充段落
    C-x f 设置填充列宽
    C-x . 设置填充前缀
    M-x auto-fill-mode 切换自动填充

    我经常在编写代码中的注释时使用段落填充 (M-q), 并且主模式通常会为该编程语言或文件类型设置最佳实践的填充宽度 (C-x f).

    考虑一下夏洛克·福尔摩斯的这句话, 它超出了页面范围:

    'It is an old maxim of mine that when you excluded [...]
    

    将光标放在段落中并键入 M-q 后:

    'It is an old maxim of mine that when you
    have excluded the impossible, whatever
    remains, however improbable, must be the
    truth.'
    

    如果你用前缀 C-u 键入 M-q, Emacs 也会尝试对齐文本:

    'It is an old maxim  of mine that when you
    have excluded the impossible, whatever
    remains, however improbable, must be the
    truth.'
    

    键入 C-x f 会提示你输入填充宽度. 例如, 对于上面的引文, 我将光标放在我希望段落断开的列上, 然后按 C-x f–大约 42 个字符. 填充宽度是每行的字符数, 但 Emacs 不会断开单词连字符, 所以不要将填充宽度设置得太小, 否则它将无法正常填充.

    填充前缀是一个有趣的特性. 当你键入 C-x . 时, Emacs 会获取当前行上光标之前的所有字符并将其作为填充前缀. 正如其名, 填充前缀会在你用 M-q 填充段落时插入到行前. 要删除填充前缀, 请将光标放在空行上并键入 C-x ..

    你可以通过启用 M-x auto-fill-mode 来让 Emacs 在你书写时自动填充文本. 我不会在编程模式中使用它 (它效果不好), 而是将其用途限制在文本模式中.

  2. Commenting

    在代码中插入注释是 Emacs 非常擅长的事情. 它确实带有一些不同的按键绑定和命令, 每个都有略微不同的用例.

    Key Binding Purpose
    M-; 注释或取消注释 DWIM38
    C-x C-; 注释或取消注释行
    M-x comment-box 将区域注释为一个框
    M-j, C-M-j 插入新行并在新行上继续注释

5.0.6. 搜索与替换

当你搜索文本时, 你可以使用正则表达式 (参见下一节" 正则表达式" ) 或不使用正则表达式. 在 Emacs 中替换文本也是如此, 但有一个额外的好处, 即让你可以在搜索和替换的替换部分利用 elisp 的强大功能.

从这个意义上说, Emacs 与其他编辑器不同: 你可以将 elisp 和 regexp 捕获组一起使用–如果你了解 elisp, 这非常强大. Emacs 的正则表达式实现也与 PCRE39 不同, 我稍后会解释. 它遵循 GNU 正则表达式标准, 并有许多附加功能 (和一些遗漏) 以使其适用于包开发者和 Emacs 用户.

  1. Case Folding

    在" Isearch: 增量搜索" 中, 我谈到了大小写折叠 (case folding), 这是 Emacs 的一个巧妙功能, 它可以智能地进行不区分大小写的字符串匹配, 除非你搜索混合大小写或大写字符串, 此时它会激活区分大小写的搜索. 这是一个很棒的功能, Emacs 的替换机制也使用它.

    考虑一个包含以下伪代码的缓冲区:

    HELLO_WORLD = "Hello, World!"
    
    function hello() {
      print(HELLO_WORLD)
    }
    

    如果我们用 C-M-% 将 hello 替换为 goodbye, 上面缓冲区的结果是:

    GOODBYE_WORLD = "Goodbye, World!"
    
    function goodbye() {
      print(GOODBYE_WORLD)
    }
    

    如你所见, Emacs 保留了每个替换匹配项的大小写, 因为我们搜索的是 hello 而不是 Hello 或 HELLO. 如果你搜索 Hello 或 HELLO, Emacs 只会替换那些文字匹配项, 因为它们包含大写字符.

  2. 正则表达式

    之前, 我提到了 PCRE 和 Emacs 之间的区别. 长话短说: Emacs 的 regexp 引擎远不如它本可以的那样用户友好. 它陈旧, 饱经风霜且根深蒂固–并且经过大量修改以适应 Emacs 的特殊需求–难以轻易替换. 例如, 在 PCRE 风格的引擎中, 字符 ( 和 ) 是元字符 (meta-characters), 这意味着引擎不会将它们视为文字字符, 而是作为捕获组 (capturing group). 在 Emacs 中, 情况正好相反. 它们是文字字符, 直到你用反斜杠 (\) 转义它们, 此时它们才扮演元字符的角色.

    在实践中, 这给不习惯 Emacs 古怪 regexp 引擎的人在构建 regexp 时造成了困惑. 如果你编写 elisp, 情况会更糟, 因为你必须转义转义字符, 否则 Emacs 的 C 风格字符串读取器会触发反斜杠.

    我不会详细介绍正则表达式, 因为那本身就是一整本书的内容. 相反, 我会告诉你 Emacs 的 regexp 引擎与现代引擎有何不同.

    1. 反斜杠结构 (Backslashed Constructs)

      以下结构需要反斜杠, 否则 Emacs 会将它们视为文字字符:

      Constructs Description
      \   备选 (Alternative)
      \(, \) 捕获组
      \{, \} 重复 (Repetition)
    2. 缺失的功能 (Missing Features)

      Emacs 不支持任何类型的否定或肯定前瞻 (lookahead) 或后瞻 (lookbehind), 除非是特定的, 硬编码的结构. 更晦涩的 regexp 功能, 如分支重置组 (branch reset groups) 等也缺失. 对于大多数文本编辑来说, 这通常不是一个大问题. 一个令人讨厌的地方是缺少 \d 这个数字类的简写. 你必须使用 [0-9] 来代替 \d, 或者使用显式类 [:digit:].

    3. Emacs 独有的功能

      Emacs regexp 引擎的一个亮点在于它对匹配结构和 Unicode 的支持:

      Constructs Description
      \<, \> 匹配单词的开头和结尾
      \_<, \_> 匹配符号的开头和结尾
      \scode 匹配其语法表代码为 code 的任何字符
      \Scode 匹配其语法表代码不是 code 的任何字符

      在编程中, 使用 \<, \> 和 \_<, \_> 匹配符号和单词对于临时重构很常见. 单词和符号的定义再次取决于 Emacs 的语法表, 因此也取决于主模式.

      \s 和 § 都有其用武之地, 因为你可以根据特定的语法类匹配字符. 每个类的命名实际上只是一个指导方针, 因为没有什么能阻止你在主模式作者的情况下声明数字 9 属于空白类 (whitespace class).

      以下是一些有趣的语法类的简略列表:

      • 空白字符 (-): 正如你所料, 包括你卑微的空格,但也包括换行符和通常的 Unicode 等效项,如不间断空格.
      • 单词构成部分 (w): 这通常包括所有大小写字母, 数字以及来自非拉丁字符集的等效 Unicode 字符.
      • 符号构成部分 (_): 包括所有单词构成部分以及其他符号, 如 ! 或 _, 在编程语言中最常用. 这个类比任何其他类都更有可能根据你的主模式而改变.
      • 标点符号 (.): 包括常见的字符,如 . 和 ;. 文本模式和编程模式可能会有很大差异.
      • 开/闭圆括号 (( 和 )): 构成组合对的任何字符集. 大多数文本和编程模式包括 (), [] 和 {}.
      • 字符串字符 ("): 标记连续块为字符串的任何符号. 双引号和单引号, ' 和 ", 通常都在其中. 诸如左右版本, 尖引号等 Unicode 字符也可能存在于此类中.
      • 开/闭注释字符 (< 和 >): 定义注释边界的任何字符或字符对. 某些语言仅支持行级注释, 在这种情况下仅使用 <.

      例如, 要匹配所有空白字符, 你应该搜索 \s-. 如果你想匹配所有字符串引号字符–例如在 Python 中, 你可以同时拥有 'strings' 和 "strings"–请使用 \s" 来匹配所有引号符号. 这使得转换 (或仅仅查找, 因为这些命令也适用于 regexp Isearch 或 Occur) 经过 Emacs 对主模式语法理解增强的文本成为可能.

      1. 确定字符的语法类

        Emacs 的 Unicode 支持非常棒, 作为其广泛 Unicode 支持的一部分, 你可以选择检查你喜欢的任何字符, 方法是使用 C-u M-x what-cursor-position. 要使用它, 请将光标放在要检查的字符上, 然后运行该命令或键入 C-u C-x =. 你会看到一系列信息, 包括语法类, 字体锁定, Unicode 名称等等.

        Emacs 中有几种可用的捕获组类型:

        Constructs Description
        \1 到 \9 插入来自组 \N 的文本
        \#1 到 \#9 插入来自组 \N 的文本, 但强制转换为整数 (仅用于 elisp)
        \? 提示用户输入文本
        \# 插入一个从 0 开始递增的数字
        \& 插入整个匹配字符串

        \#N 捕获组在 elisp 表单之外几乎没有用处. \? 允许你用手动输入的字符串替换匹配项. \# 组插入一个从 0 开始并在每次匹配后递增 1 的数字. 最后, \& 只是插入整个匹配字符串.

    4. 调用 Elisp

      你可以从搜索和替换界面的替换部分调用 elisp 函数. 你是否能找到它的用处完全取决于你对 elisp 的熟悉程度 (或者你有多愿意尝试) 以及你多久发现自己需要进行复杂的搜索和替换.

      要调用一个 elisp 表单, 你使用以下格式:

      \,(form ...)
      

      其中 form 是你想要调用的函数的名称.

      如果你想调用 elisp, 你必须遵循一些规则:

      • 捕获组默认情况下是字符串类型; 将字符串传递给期望其他类型 (如整数) 的 elisp 函数会导致错误.
      • 如果你的函数不需要捕获组, 则不需要它们. 完全可以用函数的唯一输出来替换匹配项.
      • 你只能调用一个表单, 所以如果你想调用多个表单, 你必须将其包装在类似 progn 或 prog1 的东西中, 或者使用像 concat 这样的函数将多个函数的结果连接成一个.
      • 不要引用捕获组, 因为它们作为文字字符串 (如果你使用 \N) 或数字 (如果你使用 \#N) 传递给 Emacs 的解释器.

      以下是一些你可以尝试的替换字符串示例:

      Replace String Description
      \,(upcase \N) 将捕获组 \N 大写
      \,(format "%.2f" \#N) 将 \#N 转换为数字并将其格式化为带两位小数的小数

      尽管这是一个强大的功能, 但它是视情况而定的. Emacs 的大多数内部函数–几乎所有做有趣事情的函数–都作用于缓冲区而不是字符串, 正如我在" 缓冲区" 中提到的那样. 这极大地降低了此功能的实用性, 因为你不仅需要找到一个能做你想做的事情的函数, 而且还必须找到一个作用于字符串的函数.

      当我需要这个功能时, 我不可避免地会求助于编写自己的专用函数来按我想要的方式转换文本. 但这需要一定的 elisp 流畅度. 我的建议是使用 Emacs 的键盘宏–我稍后会介绍这个主题–因为它们更适合复杂的编辑任务.

5.0.7. 修改大小写

大小写更改–将文本大写或将其转换为小写或大写–在代码和文本中都是常见的操作.

Region Commands Description
C-x C-u 将区域大写
C-x C-l 将区域小写
M-x upcase-initials-region 将区域首字母大写

关于前两个没什么好说的. 当你的区域处于活动状态时, 你可以将该区域大写或小写. 将区域首字母大写实际上意味着将区域中的每个单词首字母大写–而不仅仅是句子, 行或段落中的第一个单词.

作用于单词的大小写命令更有趣:

Key Binding Description
M-c 将下一个词首字母大写
M-u 将下一个词大写
M-l 将下一个词小写

首先, 它们是助记的, 并且绑定到你可以称之为黄金按键位置 (易于触及和键入的按键) 的位置.

它们的工作方式与 Emacs 中其他单词命令的工作方式完全相同, 并且它们遵循与 forward-word, mark-word, kill-word 和 transpose-words 命令相同的语法表规则.

数字参数和负参数都按预期工作. 与其他基于单词的命令一样, 我建议你将这些命令牢记于心. 忘记区域命令. 除非你进行大量基于区域的大小写转换, 否则你更有可能逐词更改大小写.

在使用它们时保持节奏很重要, 因为你通常会在书写时使用它们. M– M-u 会将你写的最后一个词大写, 例如, 而 M-b M– M-u 会向后移动一个词并将前面的词大写.

当然, 你在按键之间不应该释放 meta. 所以, 左手拇指按住 meta 键, 其他手指可以自由地键入 b - u.

考虑这个句子. 我想插入一个句号并大写下一个词:

█Hey how are you?

在键入 M-f 向前移动一个词后; . 插入一个句号, 然后 M-c 大写下一个词:

Hey. How█ are you?

同样, 我在这里完成了一个标识符的输入–但它应该大写, 因为它指向一个字符串常量:

print(greeting_string)

在大多数主模式中, _ 要么是标点符号, 要么是符号, 所以它会断开单词; 因此, 需要按两次 M-b 才能将光标移动到 greetingstring 的开头.

或者你可以执行 M– M-2 M-u, 它结合了数字和负参数:

print(GREETING_STRING)

只要稍加练习, 你就能做得如此之快, 如此直观, 以至于你甚至不必去想它. 另一个好处是它不会移动你的光标; 你可以自由地继续书写. 这可能看起来不是一个巨大的节省时间的方法, 但这些小事情会累积起来.

大小写命令也适用于非拉丁字符, 因为 Emacs 将大多数 Unicode 字符映射到其正确的 Unicode 类别. 在实践中, 这意味着 Emacs 知道何时遇到小写或大写字符: 希腊语: αβψδεφγ -> ΑΒΨΔΕΦΓ 丹麦语: abcdæøå -> ABCDÆØÅ

  1. Unicode 类别

    尝试 M-x describe-categories 查看所有 Unicode 类别的完整列表.

5.0.8. 计数

当你想计数时, 无需调用 wc, 因为 Emacs 完全有能力做到这一点.

Command Description
M-x count-lines-region 计算区域中的行数
M-x count-matches 计算区域中匹配模式的数量
M-x count-words 计算单词数, 行数和字符数
M-x count-words-region, M-= 计算区域中的单词数, 行数和字符数

尽管计数的方法不止一种, 但值得记住的有两个: M-x count-words, 因为与它不幸的名称所暗示的不同, 它也会计算行数和字符数. 你可能偶尔想计算区域中的内容, 在这种情况下你可以使用 M-=. M-x count-matches 会计算你指定的 regexp 模式的匹配次数, 要么从光标到缓冲区末尾 (如果区域未激活), 要么仅在活动区域中.

5.0.9. 文本操作

文本操作是 Emacs 特别擅长的一个方面, 它有各种工具来帮助你. 为了进一步处理或从日志文件中提取相关信息而整理文本文件都是 Emacs 中常见的操作. 尽管 Emacs 永远无法完全取代像 awk 和 sed 这样的专用工具或像 Python 这样的语言, 但对于中小型任务来说, 它是一个不错的选择.

  1. 可编辑的 Occur

    我之前介绍了 M-x occur, 作为一种整理与特定模式匹配的所有行的方法. 我没有谈到的 occur 模式的一个特性是编辑匹配项的能力, 并在完成后将更改提交到其原始行.

    为此, 你必须首先通过键入 e 进入可编辑的 occur 模式. 然后你可以通过键入 C-c C-c 来提交你所做的更改. 可能性是无限的. 该功能对于键盘宏和搜索和替换特别有用.

  2. 删除重复项

    你可以在 Emacs 中删除重复行, 最棒的是, 与命令行实用程序 uniq 不同, 这些行不必相邻, Emacs 就能检测到重复项. 这意味着你可以在不排序文本的情况下删除重复项.

    Universal Argument Description
    Without 删除第一个重复行
    C-u 删除最后一个重复行
    C-u C-u 仅删除相邻的重复行
    C-u C-u C-u 不删除相邻的空行

    默认情况下, M-x delete-duplicate-lines 会从顶部开始删除它遇到的第一个重复行. 使用单个通用参数, 它会从底部开始, 因此会删除最后一个.

  3. 清除和保留行

    有时你想按模式过滤区域中的行; 无论是清除 (flush) 与模式匹配的行, 还是保留 (keep) 匹配的行.

    这两个命令都作用于活动区域, 因此如果你想对整个缓冲区执行此操作, 通常会先调用 C-x h 来选择整个缓冲区.

    Command Description
    M-x flush-lines 清除 (删除) 区域中与模式匹配的所有行
    M-x keep-lines 保留区域中与模式匹配的所有行并删除所有不匹配的行

    这两个命令都接受一个 regexp 模式. 如果该模式与某一行匹配, 则该行要么被保留要么被清除–而不是模式本身 (为此, 请使用搜索和替换).

    我在处理文本时经常使用这些命令. 你可以保留与模式匹配的行, 并将其用作简陋的 grep, 而 flush 则扮演相反的角色. 过滤日志文件或清理数据是这些命令效果很好的两个例子.

  4. 复制和 Killing 匹配行

    与清除和保留行的命令类似, 你可以告诉 Emacs 它应该复制或 kill 它们. 不过, 它们是 Emacs 28 的新功能, 所以你不会在早期版本中找到它们.

    Command Description
    M-x copy-matching-lines 复制区域中与模式匹配的所有行 (Emacs 28+)
    M-x kill-matching-lines Kill 区域中与模式匹配的所有行 (Emacs 28+)
  5. Joining 和 Splitting Lines

    与作用于行的 kill 命令 (C-M-<backspace> 和 C-k) 不同, 这些命令不会改变你的 kill ring. 它们也更专业, 因为它们在不移动光标的情况下插入或删除行.

    Key Binding Description
    C-o 在光标后插入一个空行
    C-x C-o 删除光标后的所有空行
    C-M-o 在光标后拆分一行, 保留缩进
    M-^ 将光标所在的行与上一行合并

    当你想在光标后立即插入一个换行符时, 请使用 C-o. 与 RET 不同, 你的光标不会跟到下一行. 它会保持在原来的位置. 我用它来将一个段落分成两部分, 当我不想同时用 RET 移动光标时.

    删除空行是一个常见的操作. C-x C-o 正是这样做的, 但它遵循三个规则:

    • 忽略当前行: 即使当前行是空的, 它也不会删除光标所在的行. 这意味着如果你在一个空行块上调用该命令, 它总是会恰好留下一个空行. 记住这个规则, 因为这是在文本中的段落之间或代码中的类和函数定义之间保持一致间距的好方法.
    • 在光标前工作: 因此, 当你在非空行上调用它时, 它会删除光標前的空行. 与前一条规则不同, C-x C-o 会删除所有空行.
    • 仅包含空白和制表符的行也会被删除: 最适用于你经常在空行上留下制表符或空白字符的语言.

    C-M-o 是一个你不会经常使用的小众命令. 与在光标后插入换行符 (称为开辟一行) 的 C-o 不同, C-M-o 也做同样的事情, 但它会保持文本的列偏移量.

    考虑 C-o 和 C-M-o 之间的区别:

    All the world's a stage, █and all the ...
    

    在 C-o 之后:

    All the world's a stage, █
    and all the ...
    

    现在考虑原始示例, 但改用 C-M-o:

    All the world's a stage, █
                              and all the ...
    

    注意光标保持在原来的位置.

    最后, M-^ 命令与 C-o 和 C-M-o 的作用相反: 它会将光标所在的当前行与正上方的行连接起来. 如果你想将句子合并成一个大段落, 或者将多行函数参数合并成一行, 这很方便.

    M-^ 足够聪明, 可以在连接两行时修剪空白. 也就是说, Emacs 会修剪空白, 以便至少保留零个或一个空格, 具体取决于你正在合并的行上是否有文本. 对于空行, 所有空白都会被修剪, 对于有文本的行, 除了一个空格之外的所有空白都会被修剪.

    1. 填充前缀 (Fill prefix)

      在启用了填充前缀的情况下键入 C-M-o 会拆分当前行并在新行上插入填充前缀. 相反, M-^ 会删除你连接的行中的填充前缀.

  6. 空白字符命令

    当你从别处 yank 文本时, 或者如果你使用的语言或文本中空白很重要, 管理空白是一个经常出现的问题.

    Command Description
    M-x delete-trailing-whitespace 删除所有尾随空白
    M-SPC 删除光标左右两侧除 1 个空格或制表符外的所有空白
    M-x cycle-spacing 同上, 但在除一个, 全部和撤销之间循环
    M-\ 删除光标周围的空白

    M-SPC 会将光标左右两侧的所有空白修剪为单个空白字符. M-\ 也做同样的事情, 但会删除所有空白字符, 不留任何字符. M-x cycle-spacing 会在保留一个, 不保留和恢复原始间距之间循环.

    1. Cycle Spacing as the default

      你可能更喜欢在按 M-SPC 时在间距选项之间循环, 而不是默认只保留一个空格. 将此添加到你的 init file 以实现此目的:

      (global-set-key [remap just-one-space]
                      'cycle-spacing)
      

      你可以让 Emacs 可视化地显示空白字符和其他排版问题, 例如尾随空格或过长的行, 方法是使用 Emacs 的空白模式 (whitespace mode).

    2. 空白辅模式 (Whitespace Minor Mode)
      Command Description
      M-x whitespace-mode 高亮显示所有空白字符的辅模式
      M-x whitespace-newline-mode 用 $ 显示换行符的辅模式
      M-x whitespace-toggle-options 显示所有 whitespace-mode 选项的切换菜单

      Emacs 的空白辅模式会用字形和颜色覆盖原本不可见的空白字符, 以便你可以区分它们. 如果你想查找尾随空白, 错误的制表符或包含空白的" 空" 行, 这非常有用.

      空白模式会跟踪以下内容: 尾随空格, 制表符, 空格, 超过 whitespace-line-column (通常为 80 个字符) 的行, 换行符, unicode 间距, 空行, 缩进 (制表符和空格), 制表符后的空格和制表符前的空格. 基本上, 它会跟踪可能导致语法或排版错误的每一种可以想到的组合.

      我建议你自定义空白模式–特别是颜色, 因为它们有点太显眼了–通过自定义组 whitespace (使用 M-x customize-group).

      你也可以使用 M-x whitespace-toggle-options 并切换你希望 whitespace-mode 高亮的样式.

    3. 空白报告和清理 (Whitespace Reporting and Cleanup)
      M-x Command Description
      whitespace-report 显示空白问题
      whitespace-report-region 同上, 但针对区域
      whitespace-cleanup 尝试自动清理
      whitespace-cleanup-region 同上, 但针对区域

      你可以使用 M-x whitespace-report (以及类似地针对区域) 生成一份报告, 并查看缓冲区或区域中存在的" 问题" 的简洁列表. 此外, 你可以要求 Emacs 尝试使用等效的清理命令清理缓冲区或区域.

5.0.10. 键盘宏

你可以在 Emacs 中录制按键和命令, 并将其保存下来以便稍后作为键盘宏 (keyboard macro) 播放. Emacs 中的键盘宏与 LISP 宏 (LISP macro) 非常不同, 你不应该混淆两者.

宏录制并非新发明. 大多数 IDE 和文本编辑器都有此功能, 但很少有像 Emacs 中那样高级的. Emacs 的键盘宏特别强大, 因为几乎所有内容都会被录制下来. 盲点很少–你不太可能遇到–这就是它与 IDE 及其大多贫乏的宏录制功能的不同之处.

Emacs 的宏录制器本身是用 LISP 编写的. 仅此一点就说明了 Emacs 提供的可扩展性的强大, 但它也强化了你可以在微观和宏观层面检查和记录所做更改的程度.

  1. 基本命令
    Key Binding Description
    F3 开始宏录制, 或插入计数器值
    F4 停止宏录制, 或播放上一个宏
    C-x ( 和 C-x ) 开始和停止宏录制
    C-x e 播放上一个宏

    你可以用 F3 开始录制, 用 F4 停止录制. 另外两个按键不太容易按到, 它们是为了与 Emacs 的资深老用户兼容而存在的.

    当你调用通用退出命令 C-g 时, 宏录制器会停止.

    1. 信号错误 (Signaling Errors)

      有时, 你可能会在 Emacs 中触发一个错误, 这也会停止录制或播放. 一个常见的" 错误" 是在你到达错误列表的开头或结尾时使用 M-g M-n 或 M-g M-p (转到下一个或上一个错误). 然后 Emacs 会在内部发出一个错误信号, 这可能会发出哔哔声或使你的屏幕闪烁, 具体取决于你系统的保真度. 如果你不小心, 这会让你措手不及; 我仍然会遇到这种情况! 不过, 这最终是一件好事: 你可以录制一个调用下一个或上一个错误功能的宏, 然后重复播放该宏: 当它到达错误列表的末尾 (或开头) 时, 它会发出停止信号40.

  2. 高级命令

    有一个完整的前缀键组 C-x C-k, 专门用于 Emacs 的宏功能. 有许多命令, 其中大部分都相当小众.

    1. 了解更多

      与往常一样, 你可以在前缀键后附加 C-h, Emacs 会列出绑定到该前缀的所有按键. 另一种方法是使用 apropos (C-h a) 列出所有命令. 命名方案有些不一致. 许多宏命令以 kmacro 开头. 然而, 有些则早于 Emacs 的那一部分, 仅包含单词 kbd-macro.

    2. 交互式宏播放 (Interactive Macro Playback)

      让我们从计数器开始. 当你开始录制时, Emacs 会自动将内部计数器初始化为零, 并且在录制过程中每次按 F3, Emacs 都会插入计数器, 然后将内部计数器加 1. 当然, 计数器有很多创造性的用途: 创建编号列表是最明显的.

      Key Binding Description
      C-x C-k C-a 增加计数器
      C-x C-k TAB, F3 插入计数器
      C-x C-k C-c 设置计数器
      C-x C-k C-f 设置格式计数器
      C-x C-k q, C-x q 录制时查询用户输入

      上面的计数器命令做的还不止这些. C-x C-k C-a 会给计数器增加一个数字, 相反, 给它一个负数会从计数器中减去. F3 和 C-x C-k TAB 都会插入计数器值并将其加 1, 但如果你给它通用参数 C-u, 它会插入上一个数字并且不增加计数器; 如果你需要连续多次使用相同的数字, 这很有用.

      1. 计数器重置和寄存器 (Counter Resets & Registers)

        计数器仅在你明确设置它们或录制新宏时才会重置. 计数器在宏播放之间保持不变. 类似地, 你可以自由使用 Emacs 的寄存器来跟踪–以及加减–值 (如果你愿意). 它们独立于宏机制运行, 并且在宏录制时不会重置.

        命令 C-x C-k C-c 会显式设置计数器, 而不仅仅是像 C-x C-k C-a 那样增加它. 最后, C-x C-k C-f 可能是计数器命令中最先进的一个. 它接受一个格式字符串并根据该字符串格式化计数器 (有关格式字符串的更多信息, 请键入 C-h f format). 因此, 例如, 你可以将数字小数化或用前导或尾随零打印它–或者任何类似的东西, 比如插入一个纯数字和文本. 确保你传递给 C-x C-k C-c 的文本不要用引号括起来, 因为它们会自动转义.

        最突出的命令是 C-x C-k q (或简称 C-x q). 当你调用它时, Emacs 会在宏录制中标记该步骤, 并要求用户提供建议–实际上是暂时停止宏以提示用户–然后再继续.

        Query Key Binding Description
        Y 正常继续
        N 跳过宏的其余部分
        RET 完全停止宏
        C-l 重新居中屏幕
        C-r 进入递归编辑
        C-M-c 退出递归编辑

        Y 和 N 继续或停止宏的当前迭代. 因此, 如果你连续执行多个宏, N 会跳过其余部分并在宏的开头重新开始. Y 只是正常继续. RET 会完全停止宏并中止后续的宏播放.

        你可以重新居中屏幕–这与通常的 C-l 命令不同–Emacs 会将光标置于缓冲区的中间.

    3. 递归编辑 (Recursive Editing)

      递归编辑是一个高级主题. 当你进入递归编辑 (C-r) 时, Emacs 会暂停任何正在进行的命令–例如 Isearch, 搜索和替换或宏–并将控制权交还给你 (用户). 然后你可以自由地继续编辑并以其他方式正常使用 Emacs, 但在任何时候你都可以键入 C-M-c, Emacs 会跳回到你进入的先前递归步骤并从那里继续. 你可以根据需要嵌套任意多次递归编辑, 如果你处于递归编辑中, 你可以在模式行中看到它, 因为会出现方括号 ([]) . 你可以通过键入 ESC ESC ESC 来强制 Emacs 放弃所有递归编辑级别. 注意, 与大多数其他情况不同, C-g 不会退出递归编辑. 那么, 你在实践中如何使用它呢? 一个例子是在 Isearch 或宏播放期间意识到你需要编辑文本, 发送电子邮件或以其他方式暂时暂停你正在做的事情. C-r 让你做到这一点. 完成后, 键入 C-M-c 以从之前离开的地方继续. 这是一个非常强大的功能, 一旦你掌握了本书中的所有其他内容, 就值得了解.

  3. 保存和调用 (Saving and Recalling)

    Emacs 中的宏存储在一个宏环 (macro ring) 中, 这个概念你应该从 Emacs 的其他部分 (如 kill ring 和 undo ring) 中认识到. 创建一个新宏会自动将旧宏存储在宏环中, 你无需执行任何操作. 下面的命令可让你从宏环中保存和调用, 编辑宏, 将宏绑定到按键等等.

    Key Binding Description
    C-x C-k C-n 循环到宏环中的下一个宏
    C-x C-k C-p 循环到宏环中的上一个宏
    C-x C-k n 命名上一个宏
    C-x C-k b 将上一个宏绑定到一个键
    C-x C-k e 编辑上一个宏
    C-x C-k l 编辑最后 300 个按键
    M-x insert-kbd-macro 将宏作为 elisp 插入

    C-x C-k C-n 和 C-x C-k C-p 都会在宏环中循环. Emacs 会很方便地显示宏的一部分, 以便你知道哪个是活动的.

    你可以使用 C-x C-k n 命名宏. 如果你想将宏保存到文件, 必须先执行此操作, 因为然后你可以打开你的 init file 并调用 M-x insert-kbd-macro, Emacs 会插入一个代码生成的宏版本. 命名和插入的宏会变成你可以用 M-x 调用的命令. 你也可以仅针对当前会话临时将其绑定到一个键, 方法是使用 C-x C-k b.

    1. 命名事项 (Naming Things)

      我建议你选择一个一致的命名方案. 你不能在 LISP 中使用空格, 但几乎可以使用任何其他字符. 不过, 我总是以我的姓名缩写 mp- 开头, 这样我就可以将其与 C-h a 和朋友们结合起来. 现在我可以用 Emacs 自己的自文档化工具找到我所有自制的命令.

      如果你犯了错误, 你可以编辑现有的宏. C-x C-k e 命令会打印一个宏命令列表, 你可以像编辑文本一样编辑它们. 你可以用 C-c C-c 提交这些更改.

    2. 记录丢失 (Lossage)

      Emacs 会记住你输入的最后 300 个字符和命令, 称为 lossage. 你可以通过键入 C-h l 查看此字符列表. 你甚至可以保存你在 Emacs 中进行的每一次按键操作–包括密码等敏感信息, 所以要小心–方法是键入 M-x open-dribble-file. Lossage 还会显示按键所属的命令. 缓冲区中活动模式之类的东西可能会更改按键所属的命令. 因此, 如果你在缓冲区和模式之外查找按键, 请记住这一点. 当你想追溯性地制作键盘宏时, 请务使用 lossage 功能, 因为你可以使用命令 M-x kmacro-edit-lossage 从你的输入中创建宏.

  4. 键盘宏的实际用途 (Practical Uses for Keyboard Macros)

    当你提到键盘宏时, 首先想到的是简单的文本自动化, 但 Emacs 的宏系统能够做的远不止这些.

    一个被忽视的方面是能够组合许多复杂的任务, 然后命名并保存这些宏以供以后重用. 就其本身而言, 这可能不是一个令人惊讶或值得注意的功能, 直到你意识到你可以编排许多你原本需要深厚的 Emacs 或 elisp 知识 (你可能还没有!) 才能完成的繁琐工作, 而无需使用宏.

    • 完全按照你的喜好配置窗口设置: 如果你想打开某些文件并以特定方向显示它们, 你可以使用键盘宏来完成此操作. 首先用 C-x 1 清除窗口, 然后使用宏打开你想要的文件; 根据你的喜好拆分窗口并将它们切换到你关心的缓冲区; 然后结束宏. 现在, 你可以随时调用此设置.
    • 为频繁的文本编辑操作编写宏: 也许你有一系列文本模式想要批量更改, 或者你希望它根据当天的工作量是半动态的. 例如, 你可以使用 M-x rgrep 查找需要更改的文件选区, 并使用键盘宏逐步处理 grep 缓冲区中的每个条目并应用搜索和替换模式. 只需开始录制并键入 M-g M-n 从 Emacs 正在提取结果的任何来源中选择下一个匹配项; 执行你的文本编辑; 然后结束录制. 现在, 每次用 C-x e 调用宏时都会处理一个条目, 并且它足够通用, 你可以将 grep 替换为 occur, M-x compile 以及任何其他使用 M-g M-n / M-g M-p 的来源.
    • 将复杂的决策推迟给人类: 你可以用 C-x C-k q 插入查询步骤, 因此如果你知道在宏可以继续之前有模棱两可或复杂的选择, 你可以在宏的关键点自由地穿插它们, 并决定是否应该退出宏; 跳过更改; 或进入递归编辑层以手动编辑缓冲区, 然后再继续使用宏. 你甚至可以四处移动光标, 或在恢复之前更改缓冲区文本.
    • 从宏中调用宏: 你可以从宏中调用宏: 那么为什么不创建小的, 可重用的宏逻辑片段, 然后制作一个调用这些宏的宏呢. 一旦命名–即使它们没有命名, 使用宏环–你就可以随意调用它们.
    • 用合理的默认值填充提示: 如果你发现自己一遍又一遍地重复相同的命令, 你可以将它们包装在一个快速的键盘宏中. 你甚至可以在提示中插入查询标记, 并通过在录制期间使用 C-r 进入递归编辑来覆盖你输入的任何默认值; 更改你在提示中键入的内容; 然后在你完成 C-M-c 后返回到宏. Emacs 会无缝地让你修改一个活动的宏; 即使它在一个提示中. 提示显示在微缓冲区中, 它实际上也是一个缓冲区. 这再次突出了 Emacs 中概念的对称性和重用性.

    宏非常值得学习, 正如你所见, 当你不知道如何仅用 elisp 或自定义来解决问题时, 它可以作为一种灵活的变通方法. 只需记住, 如果你能像人一样通过与 Emacs 交互来完成某件事, 你就可以录制一个宏让 Emacs 重复它.

5.0.11. 文本展开

Emacs 中有几个内置工具–以及同样多的第三方工具–可以展开文本. 它们都有略微不同的目的, 但目标都是最大限度地减少输入并最大限度地实现自动化.

以下是 Emacs 中可用的一些工具:

  • Abbrev: 展开缩写–例如将 func 展开为 function–可以在特定模式或全局级别进行. 这是一个非常简单的展开机制, 其主要优点是它在你键入时悄无声息地进行, 修复拼写错误或展开缩写. 与 Emacs 的许多其他功能一样, 几乎没有图形化的仪式: 当它展开键盘快捷键时, 没有旋转的图形或其他视觉混乱来分散你的注意力–事实上, 除非你正在寻找它, 否则你不太可能注意到它. 你通常会用它来进行明确的更正, 例如更正拼写错误.
  • DAbbrev, 或动态缩写 (dynamic abbreviations): 与 Abbrev 类似, 但它通过动态查找光标处的词可能扩展成的内容来扩展前一个词. 例如, 在一个包含许多函数定义的缓冲区中键入 func, DAbbrev 会在你手动触发扩展机制时自动将其扩展为 function.
  • Hippie expand: 一个功能更强大的 DAbbrev 替代品, 它不仅可以扩展单词, 还可以扩展整行, lisp 符号, Abbrev 缩写, 文件名和文件路径. 这个功能非常强大, 它是 Emacs 自带的默认 DAbbrev 的直接替代品.
  • Skeletons: 一个复杂的模板工具, 它将简单的 elisp 原语–提示, 区域包装, 缩进和光标定位–与类似 Abbrev 的扩展相结合. 尽管它作为 Emacs 的核心部分已经存在了 20 多年, 但很少有人使用它. 这确实很可惜, 因为它非常强大, 但它需要耐心或 elisp 知识才能使用, 所以几乎没有人使用它.
  • Tempo: Emacs 自带的另一个模板工具. 它类似于 Skeletons.
  • YASnippet: 一个受文本编辑器 TextMate 模板工具启发的第三方包模板工具–而 TextMate 本身也大量借鉴了之前的其他工具. 它使用一种简单的模板语言来创建你可以用 tab 或空格触发并扩展为可编辑模板的片段. 它类似于 Skeletons, 但可以说更容易使用.
  • Autoinsert: 插入模板–很像 skeletons–当你创建一个与特定文件类型匹配的新文件时. 当你想自动生成文件的样板内容时, 这非常有用, 例如 HTML 标签 (如 html, head 等).

在以上所有选择中, 我会把注意力集中在用于模板化的 YAsnippet 上, 因为它为许多主模式提供了大量的片段, 并且 Hippie Expand 是一个很棒的生产力提升器.

除非你有特殊原因, 否则 Tempo 和 Skeletons 都不值得今天学习. Abbrev 仅适用于单词替换, 因为它缺乏我上面谈到的更高级文本扩展工具的功能. 当你将 YASnippet 和 Hippie Expand 集成到你的工作流程中后, 如果你觉得需要, 可以添加 Abbrev 和 Autoinsert.

  1. Abbrev

    Abbrev 是文字处理器中自动更正样式功能的完美工具. 我用它来替换常见的拼写错误并将 resume 这样的词替换为 résumé. 然而, 如果你想用它做更高级的事情, 例如软件开发中使用的复杂文本扩展, 那么它无疑是错误的选择.

    abbrev 之所以有效, 部分原因在于它的简单性: 它在扩展单词时没有任何视觉干扰–事实上, 我很少注意到它在更正单词.

    Key Binding Description
    C-x a l 添加特定于模式的 abbrev
    C-x a g 添加全局 abbrev
    C-x a i l 添加特定于模式的反向 abbrev
    C-x a i g 添加全局反向 abbrev

    当你用 C-x a g 或 C-x a l 添加一个 abbrev 时, Emacs 会查看光标前的单词并将其用作替换词–也就是说, 而且我自己也搞糊涂了, 是你希望它扩展成的词, 而不是触发词. 所以, 要将 resume 替换为 résumé, 你需要键入 résumé, 将光标放在该词之后, 然后键入,比如说, C-x a g 并输入 resume. 当你在键入 resume 后按 SPC 时, Emacs 会将其替换为 résumé.

    反向命令则相反. 你键入单词 resume, 输入 C-x a i g, 回答 résumé, Emacs 会将 resume 扩展为 résumé.

  2. DAbbrev 和 Hippie Expand

    Hippie Expand 很棒. 它具有近乎超自然的能力, 可以将光标处的文本扩展成你想要的内容.

    在谈论 Hippie Expand 之前, 让我们先谈谈如何使用 DAbbrev, 它是 Emacs 中功能较弱的表亲和默认的动态缩写工具:

    Key Binding Description
    M-/ 展开光标处的词
    C-M-/ 展开, 然后显示补全项

    M-/ 这个键很容易键入, 重复按会循环浏览选项列表. 重复该命令足够多次, 它会恢复到原来的词. 如果有很多选项可供选择, C-M-/ 命令会尝试尽可能多地补全, 并在仍然有多个选项时显示一个补全列表.

    DAbbrev 并不聪明. 它会查看你缓冲区中的其他词, 并尝试将光标处的词补全为其中一个. 这并不能使其毫无用处–它有其用武之地–只是 Hippie Expand 要好得多.

    要有效地使用 Hippie Expand, 你应该替换 DAbbrev, 因为这两者–尽管可以同时使用–实际上根本无法相互补充. 将此添加到你的 init file 中以切换到 Hippie Expand:

    (global-set-key [remap dabbrev-expand] 'hippie-expand)
    

    Hippie Expand 不仅仅扩展单词. 变量 hippie-expand-try-functions-list 是一个有序的扩展函数列表, 当你调用 M-/ 时, Hippie Expand 会用光标处的文本调用这些函数.

    我最喜欢 Hippie Expand 的一点是文件名补全. 它的工作方式与 shell 的 TAB 补全完全一样: 你键入 M-/ , Hippie Expand 会尝试补全光标处的文件名或目录. 如果你发现自己经常在代码, 配置文件或文档中插入绝对路径或相对文件名–Hippie Expand 会让你的生活轻松得多.

    另一个很棒的功能是它能够补全整行. 如果它没有思路了, 它会回退到单词补全, 如果你经常编写 elisp, 那么 Hippie Expand 会猜测光标处的文本是否是潜在的 elisp 符号, 并自动为你补全它.

    与 DAbbrev 一样, 重复调用 M-/ 会循环浏览所有潜在的匹配项, 但 C-M-/ 仅显示 DAbbrev 找到的补全项–Hippie Expand 没有等效的补全列表.

    积极使用 M-/ 需要一些练习. 你需要培养对调用它时适用的各种扩展规则的亲和力. 学习 Hippie Expand 非常值得, 因为它可以极大地节省时间.

    1. 自定义 Hippie Expand

      你可以更改 Hippie Expand 扩展文本的方式. 为此, 请自定义变量 hippie-expand-try-functions-list, 但如果你想添加新的 try 函数, 你必须知道它的名称. 要查找 try 函数的列表, 你应该:

      • 阅读源代码中的注释 (M-x find-library, 然后输入 hippie-exp 并阅读文档). 但你如何找到库名呢? 一个好的起点是描述该命令–hippie-expand–Emacs 会告诉你找到该符号的库的名称.
      • 使用 Apropos. 查看 try 函数的名称并在 M-x apropos-function 中搜索可能的函数.

      与往常一样, 这两种方法会产生不同的答案, 所以两者都试试.

5.0.12. 缩进文本和代码

当新的编程语言出现时, Emacs 的一个进行基本语法高亮和缩进的主模式几乎会立即出现. 使之成为可能的部分原因是不仅能够继承 (或重用) 其他主模式的缩进引擎, 而且还能够使用 Emacs 中存在的通用缩进引擎.

默认情况下, Emacs 会尝试根据你使用的主模式的规则进行缩进. 在旧版本的 Emacs 中, 你必须告诉它这样做, 因为它很少会默认这样做–这对新用户来说是一种令人沮丧的体验. 现在这已不成问题. Emacs 会使用一种称为 electric indentation 的功能自动应用缩进. 如果你想关闭它, 你可以自定义 electric-indent-mode.

缩进引擎的确切工作方式取决于主模式–以及其创建者的奇思妙想和个人品味. 你可能会发现它的缩进方式不合你的胃口; 特别是如果你正在编写类似 C 的语言. 你需要查阅 Emacs 的手册 (如果主模式随 Emacs 一起提供) 或者可能是源代码文件中的文档或注释. Emacs 没有一种万能的方法来自定义缩进: 你需要参考文档以获取有关如何执行此操作的说明.

  1. TAB: 缩进当前行

    当你按 TAB 时, Emacs 通常会调用 indent-for-tab-command, 这是一个通用的代理命令, 它要么缩进你的代码, 要么尝试在光标处进行 TAB 补全.

    Key and Command Description
    TAB 使用主模式的缩进命令缩进行
    M-i 插入空格或制表符到下一个制表位
    M-x edit-tab-stops 编辑制表位

    一些主模式会覆盖 TAB 键, 转而调用它们自己的专用缩进命令–一个例子是 C 主模式. 然而, 按 TAB (或 M-x indent-for-tab-command) 如果其启发式方法确定应该缩进, 则会调用存储在变量 indent-line-function 中的缩进函数. 这里的好处是 indent-for-tab-command 的通用性–它只是将工作传递给补全命令或缩进命令.

    变量 tab-always-indent 控制 Emacs 在你按 TAB 时的行为. 通常, 它只是缩进, 但它也有一个补全机制, 尽管很少使用.

    1. 禁用制表符

      如果你不喜欢使用制表符, 并且更喜欢空格, 请自定义变量 indent-tabs-mode.

      最后, 当 Emacs 缩进时, 它会调用 indent-line-function 中的上述函数. 默认函数是 indent-relative, 这是一个插入实际制表符的命令. 像 text-mode 和 fundamental-mode (新空缓冲区的默认模式) 这样的模式使用 indent-relative. 大多数编程模式不使用.

    2. 更改缩进量

      变量 tab-width 控制每个制表符使用的间距字符数. 如果你禁用了 indent-tabs-mode, 它也控制要使用的空白量.

      Emacs 中也有制表位 (tab stops) 的概念, 你可以通过键入 M-x edit-tab-stops 来编辑制表位, 并在你希望 Emacs 设置制表点的位置插入 : 字符. 随后调用 M-i (它会调用命令 M-x tab-to-tab-stop) 然后会通过空白和制表符插入制表位.

  2. 缩进区域

    区域 (Regions) 更难缩进. 当块缩进决定程序流程时, 你如何安全地缩进 Python 代码区域? 答案是–你不能. 有两种类型的区域缩进命令: " 智能" 命令会向你的主模式的缩进引擎寻求建议–这对于像 HTML 或 C 这样的语言效果很好–以及其余的普通固定宽度缩进.

    Key and Command Description
    TAB 按主模式缩进行或区域
    C-M-\ 使用主模式的区域缩进命令进行缩进
    C-x TAB 严格缩进

    在理想情况下, 按下带有活动区域的 TAB 就足以重新缩进它. 不幸的是, Emacs 可能不支持这一点, 或者在某些编程语言中, 物理上不可能确定正确的缩进. 按下 TAB 遵循与行缩进相同的大部分规则: Emacs 尝试根据 indent-line-function 进行缩进, 如果失败, 则回退到简单地插入 TAB 字符 (或者如果你禁用了 indent-tabs-mode, 则插入空格).

    键入 C-M-\ 会显式缩进该区域; 对于某些模式, 它的工作方式与 TAB 完全相同, 而在其他模式中则不然. 如果你给该命令一个数字参数, 它会将该区域缩进到该列 (即字符数), Emacs 还会使用你的填充前缀 (如果你有的话) 并填充文本.

    C-M-\ 值得你花时间, 因为它尊重你的填充前缀. 然而, 如果你想缩进固定数量的列, 你应该使用 C-x TAB.

    C-x TAB 会显式地将区域缩进一定数量的列. 它也接受负参数和数字参数. 然而, 如果你不传递参数, Emacs 会进入一个由箭头键驱动的缩进模式, 让你用 S-<left> 和 S-<right> 交互式地缩进该区域.

5.0.13. 排序和对齐

排序和对齐文本都是常见的操作, Emacs 有自己的一套命令来完成这两项工作.

  1. 排序

    Emacs 中的排序很像命令行实用程序 sort. 所有命令都对行进行排序, 除了唯一的段落命令.

    Command Description
    M-x sort-lines 按字母顺序排序
    M-x sort-fields 按字段的字典序排序
    M-x sort-numeric-fields 按字段的数字顺序排序
    M-x sort-columns 按列的字母顺序排序
    M-x sort-paragraphs 按段落的字母顺序排序
    M-x sort-regexp-fields 按 regexp 定义的字段的字典序排序

    M-x sort-lines 按升序排序, 但如果你用通用参数调用它, 它会反转排序顺序.

    当你按行排序时, Emacs 会调用 sort (因为它快得多), 除非你在 Windows 上, 在这种情况下 Emacs 会在内部执行此操作.

    1. 字典序和数字序 (Lexicographic and numeric)

      字典序排序是大多数排序算法通常的工作方式. 它们查看每个字符的字符代码并按这些代码排序. 这对于大多数情况都适用, 除了数字. 从字典序来看, 数字 4 在数字 23 之后, 因为 4 的序数大于 23 中 2 的序数. 要正确排序你的数字, 你必须使用 M-x sort-numeric-fields.

      你可以使用 M-x sort-fields 和 M-x sort-numeric-fields 按字典序或数字序排序. 不过你必须选择一列. 为此, 请传递一个数字参数 (从 1 开始) 以按该列排序. 列由空格分隔; 一个或多个空格共同表示一个列分隔符.

      因此, 要按第三列排序, 请键入 M-3 M-x sort-fields. 你只能按一列排序, 正如我之前提到的, 每列必须由空格分隔. 要更改列分隔符, 你必须使用 M-x sort-regexp-fields.

      使用 M-x sort-columns 按列排序是按多于一列排序的唯一方法, 并且仅限于连续的列. 要使用它, 请将光标和标记放在要排序的起始和结束列中, 然后从光标到标记的所有行都会被排序.

      如果你发现自己需要对非空格分隔的内容进行排序, 你必须使用 M-x sort-regexp-fields. 这个命令相当复杂, 因为它需要良好的 elisp 工作知识; 也很容易只对一个区域进行部分排序, 这会弄乱你的文本.

      考虑这个产品的 CSV 文件:

      Price,Product
      $3.50,Cappuccino
      $4.00,Caramel Latte
      $2.00,Americano
      $2.30,Macchiato
      ...
      

      你无法使用其他排序命令对这些数据进行排序, 因为它们不起作用; 数据不是以空格分隔的. 要对此进行排序, 我们需要 M-x sort-regexp-fields.

      Emacs 的内部排序例程需要一个键 (key)–即它用来排序的依据, 例如一个字段–和一个记录 (record), 通常是整行.

      以下是如何对上面的示例进行排序:

      M-x sort-regexp-fields
      Record: ^\([^,]+\),\([^,]+\)$
      Key: \1
      

      这首先将记录定义为两个捕获组, 每列一个, 用逗号分隔. 下一步是选择键–在本例中是包含价格的第一列–来进行排序.

      结果如下所示:

      Price,Product
      $2.00,Americano
      $2.30,Macchiato
      $3.50,Cappuccino
      $4.00,Caramel Latte
      

      按正则表达式排序不是你经常需要做的事情, 但当你需要时, 它是一个强大的工具. 一个重要的注意事项是, 有可能对一行进行部分排序; 如果你的搜索词如下所示:

      M-x sort-regexp-fields
      Record: ^\([^,]+\)
      Key: \1
      

      如果你对原始文本进行排序, 输出如下所示:

      $2.00,Cappuccino
      $2.30,Caramel Latte
      $3.50,Americano
      $4.00,Macchiato
      

      注意, 我们确实对第一列进行了排序, 是的, 但第二列保持不变! 也就是说, 我们对价格进行了排序, 但没有对相关的产品进行排序. 小心.

  2. 对齐 (Aligning)

    Emacs 中的文本对齐包括对齐 (justification) 和分栏文本 (columnated text). 事实上, Emacs 中的对齐引擎非常复杂, 能够根据 regexp 模式自动对齐和调整代码.

    Command Description
    M-x align 根据对齐规则对齐区域
    M-x align-current 根据对齐规则对齐节
    M-x align-regexp 根据 regexp 对齐区域

    对齐命令作用于区域 (regions), 你现在应该已经熟悉了; 或者作用于节 (sections), 这是一个某些对齐命令 (如 M-x align-current) 特有的虚构概念. 节 (section) 是一组连续的行, 其中第一个匹配的对齐规则适用. 因此, 如果有一个对齐字符串常量的规则–例如 HELLOWORLDCONST = "Hello World"; 中的 = –那么它的节将是所有匹配该规则的连续行.

    Emacs 中有许多内置的对齐规则, 当你对一段文本调用 M-x align 时, Emacs 会扫描对齐规则列表, 并找到第一个匹配所有条件的规则: 主模式, 要尝试对齐的 regexp 等等. 不幸的是, 对齐规则难以阅读和理解, 在实践中这意味着该功能不像它本可以的那样有用. Emacs 中的每个对齐规则–存储在 align-rules-list 中–都需要深入了解 regexp 并渴望剖析各个层面以弄清楚规则是如何工作的. Emacs 维护者在这里错失了一个机会, 没有要求每个对齐规则都提供解释其工作原理的文档字符串.

    M-x align-current 的好处在于你不必先标记一个区域. 它会从光标所在的行判断出适用哪个规则, 并将其应用于相邻的行 (如果它们也匹配该规则).

    以下是 Emacs 中的一些内置规则, 按主模式组织:

    • Python: 你可以像这样对齐赋值–注意 = 的对齐:

      UNIVERSE_ANSWER_CONST = 42
      UNIVERSE_QUESTION     = "What is The Answer ..."
      
    • Lisp: 你可以像 Python 示例一样对齐 alists:

      ((universe-answer . 42)
       (universe-question . "What is The Answer..."))
      

    在这两种情况下, 我都将光标放在任一行上并键入 M-x align-current, Emacs 就判断出适用哪个规则.

    尽管自动对齐很有用, 但你的场景不太可能完美匹配 Emacs 的任何对齐规则. 对于所有其他情况, 你必须使用 Emacs 灵活的 M-x align-regexp 并告诉 Emacs 你希望如何对齐文本.

    当你使用 M-x align-regexp 时, 有两种操作模式: 新手模式 (novice mode), 这是你运行命令时看到的模式; 以及复杂模式 (complex mode), 当你用 C-u 调用它时. 你唯一可能真正使用复杂模式的情况是当你想在同一行上进行多列对齐时. 令人讨厌的是, 该功能在新手模式下不可用.

    考虑以下文本:

    Cappuccino $2.00
    Caramel Latte $2.30
    Americano $3.50
    Macchiato $4.00
    

    要对齐文本并将价格与 $ 对齐, 请使用 M-x align-regexp:

    Align regexp: \$
    

    输出结果:

    Cappuccino    $2.00
    Caramel Latte $2.30
    Americano     $3.50
    Macchiato     $4.00
    

    如果你想对齐多列, 情况会变得更复杂. 考虑这个 CSV 文本:

    Price,Product,Qty Sold
    $2.00,Cappuccino,289
    $2.30,Caramel Latte,109
    $3.50,Americano,530
    $4.00,Macchiato,20
    

    要对齐所有三列, 你必须使用复杂模式. 因此, 键入 C-u M-x align-regexp. 你首先会注意到的是预先填写的建议:

    Complex align using regexp: \(\s-*\)
    

    该 regexp 匹配–在一个捕获组中–零个或多个空白字符. 它这样做的原因是因为你想要对齐的文件可能已经有很多空白 (也许你不久前对齐过它, 但由于你更改了文本, 它现在未对齐), 所以 Emacs 必须匹配并捕获你想要对齐的字符周围的现有空白, 然后再正确地重新对齐它. 当你使用新手模式时, Emacs 会自动在你想要对齐的字符之前插入该 regexp; 这意味着你对齐字符之前的任何空白都会被移除–所以即使在新手模式下, 空白捕获组也存在.

    所以, 要在 ',' 上对齐, 你必须在现有 regexp 的开头或结尾添加 ',' . 你放置它的位置会改变对齐结果:

    • 放在前面, Emacs 会在 ',' 之后插入空格进行对齐. 你可能想对像 ',' 这样的符号执行此操作. 如果不这样做, 它看起来会像这样:

      Fooooo ,Bar
      Bizz   ,Buzz
      
    • 放在后面, Emacs 会在 ',' 之前插入空格进行对齐. 你可能想对像 $ 这样的符号执行此操作. 如果不这样做, 它看起来会像这样:

      Foobar Widget $ 15.00
      Fizz Buzz   $ 10.00
      

    所以, 对于这个, 你想这样回答提示:

    Complex align using regexp: ,\(\s-*\)
    

    接下来, 选择默认答案:

    Parenthesis group to modify (justify if negative): 1
    

    只有一个捕获组, 不过对于复杂的对齐操作, 你很可能会有多个组.

    最后是间距. Emacs 会使用 align-default-spacing, 它默认为 Emacs 内部使用的制表位. 通常可以安全地将其保留为默认值, 但你可以输入一个绝对间距的数字, Emacs 会尝试遵循它:

    Amount of spacing (or column if negative): 1
    

    接下来–这是你可能真正关心的–是 Emacs 是否应该在整行重复该命令. 如果你希望 Emacs 对齐所有 ',' 符号, 请回答 yes:

    Repeat throughout the line: yes
    

    输出现在看起来像这样:

    Price, Product,      Qty Sold
    $2.00, Cappuccino,   289
    $2.30, Caramel Latte,109
    $3.50, Americano,    530
    $4.00, Macchiato,    20
    

    Emacs 的对齐命令功能强大且实用, 如果你经常处理未格式化的文本或代码. 唯一的缺点是你必须费力地通过复杂模式才能在单行上重复对齐过程多次.

    之前我说过, 你可能想用宏来自动化你经常使用的复杂命令–这肯定会是其中之一!

5.0.14. 其他编辑命令

  1. Zapping Characters

    Kill 命令在结构化文本上效果很好; 它们作用于语法单位. 但有时你想 kill 到任意字符. zap 命令 M-z 正是这样做的. 当你调用它时, 系统会要求你输入光标前的一个字符. 然后 Zap 会 kill 到 (并包括) 你输入的字符:

    http://www.example.com/█articles/?id=10
    

    在 zapping 到 / 之后:

    http://www.example.com/█?id=10
    

    与之前的 kill 命令类似, 它也会追加到 kill ring. 因此, 你可以将其与 kill 命令以及负参数和数字参数结合使用, 以控制要执行的连续 zap 次数以及执行方向.

    1. Zap up to char

      在 Emacs 28 中, 此命令现在已内置, 但默认情况下未绑定到任何内容. 一个可能的按键绑定是 M-S-z:

      (global-set-key (kbd "M-S-z") 'zap-up-to-char)
      

      但如果你更喜欢 zapping 到该字符, 你可以重新映射 C-z:

      (global-set-key [remap zap-to-char]
                      'zap-up-to-char)
      
  2. 拼写检查

    在 Emacs 中有几种拼写检查的方法, 它们都有不同的用例. 令人惊讶的是, Emacs 本身并不执行拼写检查. 对于 Linux, 可选的有 aspell 和 ispell, Emacs 会选择 aspell 而不是 ispell, 因为它更快更现代.

    Keys and Commands Description
    M-$ 检查光标处的单词
    M-x flyspell-mode 高亮显示拼写错误的辅模式
    M-x flyspell-prog-mode 同上, 但仅高亮显示代码中的字符串和文档字符串
    M-x ispell-buffer 对缓冲区运行拼写检查
    M-x ispell-region 对区域运行拼写检查

    无论你使用哪个拼写检查器, 在 Emacs 中都统称为 ispell.

    1. Windows 上的拼写检查

      你需要在 Windows 上自行安装41 aspell 或 ispell 才能使用此功能.

    2. Customize

      如果你经常使用此功能, 我建议你对其进行自定义–特别是如果你有特定的词典要求, 而不是使用默认的 ispell 组自定义的词典.

    3. 词典查找 (Dictionary Lookup)

      Emacs 28 添加了对词典查找的支持, 条件是你要么托管自己的服务器, 要么同意将单词请求发送到 dict.org. 远程服务器功能出奇地丰富, 支持多种语言.

      Commands Description
      M-x dictionary-lookup-definition 查找光标处的单词
      M-x dictionary-search 搜索给定单词
      M-x dictionary-select-dictionary 选择要搜索的词典
  3. Quoted Insert

    如果你发现自己需要插入文字 TAB, RET 或 ASCII 控制码字符, 那么你需要 quoted insert, 它绑定到 C-q.

    1. 换行符与回车符 (Line feed vs carriage return)

      如果你想插入一个文字换行符, 请键入 C-q C-j, 因为那是换行符–LINE FEED–符号, 而不是你的回车键 (它是一个 CARRIAGE RETURN).

      Quoted insert 足够聪明, 可以使用 face escape-glyph42 高亮显示 ASCII 控制码, 以便你可以直观地发现它们. Quoted insert 会对你输入的任何字符进行文字插入–例如, C-q ESC 会插入 ASCII 控制码 ^[, 也称为 ESCAPE.

6. 第六章 Emacs 实践

" […] Emacs 在大约与正午的太阳使星星黯然失色相同的程度上超越了所有其他编辑软件. 它不仅仅是更大更亮; 它简直让其他一切都消失了." – Neal Stephenson, <In the Beginning… was the Command Line>

在前面的章节中, 我几乎只讨论了 Emacs 的理论方面. 然而, 激发你的大脑并找到实用或新颖的应用是另一回事; 对大多数人来说, 理论是不够的. 在这最后一章中, 我将向你展示我称之为工作流程 (workflow) 的东西–涵盖特定领域或问题的深度演练.

与前两章不同, 我不会详细介绍本章中引入的命令和功能. 我把这些留给你自己去发现. 如果你仍然不确定如何发现新功能, 请继续阅读–本章的第一部分是" 探索 Emacs" .

6.0.1. 探索 Emacs

要真正掌握 Emacs, 你必须学会如何查找信息. 这是 Emacs 的阿尔法和欧米伽. 手册, 书籍和博客文章都对你的编辑环境–关于 Emacs–做出了假设. 一旦你更改变量, 重新绑定按键或根据自己的需要调整 Emacs, 你就创建了一个几乎没有其他人拥有的独特更改组合. 因此, 要诊断问题, 或者修复和更改你不喜欢的东西, 你必须首先知道如何找到那些东西.

让我们来探索 VC, Emacs 的版本控制界面. VC 系统是 Emacs 中一个强大但未被充分利用的功能, 它提供了一个通用界面–用于诸如版本历史, 追溯, 提交, 推送和拉取之类的操作–然后与你选择的版本控制系统进行通信. 如果你经常使用多个版本控制系统, VC 非常有用.

如果你之前不知道 VC, 并且你第一次接触它是在现在阅读这篇文章, 你会如何了解它呢?

  1. 阅读手册

    不足为奇的是, Emacs 的手册写得很好而且内容广泛. 让我们从打开 Emacs 关于版本控制的手册开始.

    1. 通过键入 C-h i 打开 M-x info 手册.
    2. 导航到 Emacs 超链接并打开它.
    3. 用 C-s 搜索 version 或 version control. 瞧, 如果你按 C-s 足够多次, 你最终会找到版本控制手册.

    所以, 阅读手册效果很好–但并非每个功能都有手册. 而且章节可能深埋在难以触及的子子子章节中. 第三方软件包几乎从不附带 info 手册.

    1. Apropos for info manuals

      可以使用命令 M-x info-apropos 加上搜索模式, Emacs 会抓取所有已知的 info 手册页面以查找匹配的模式. 如果你不确定某物在哪里, 这个命令是一个强大的工具.

  2. Using Apropos

    在 Apropos 中, 我列举了许多使用 apropos 查询 Emacs 文档系统的方法. 其中一个 apropos 命令会搜索文档字符串 (doc string)–Emacs 中大多数变量和函数附带的文档字符串–并列出匹配的函数或变量. 搜索 Emacs 的文档字符串是查找信息最分散的方法: 你实际上是在搜索纯文本文档. 为此, 请使用 C-h d, 这是搜索文档的 apropos 命令.

    1. Emacs Lisp 中的命名空间 (Namespacing)

      Emacs Lisp 与其他 lisp 不同, 它缺乏命名空间 (namespacing). 在 Emacs 中没有使用模块或命名空间来分离关注点. 在实践中, 这并不是一个大问题 (还有更大的问题需要解决), 但这确实意味着, 非正式地, Emacs 中的包会为其符号 (函数, 变量等) 添加前缀, 以免发生冲突. 例如 python- 用于 Python 主模式; apropos- 用于与 apropos 相关的命令, 等等.

      尽管如此, 如果你用 C-h d 搜索 version control, 第一个结果是:

      vc-mode
      
      Function: Version Control minor mode. This
      minor mode is automatically activated whenever
      you visit a file under control of one of the
      revision control systems in
      `vc-handled-backends'.
      
      VC commands are globally reachable under the
      prefix `C-x v':
      

      我们现在有线索了. VC 模式是 vc-mode. 然而, 我们想要它使用的前缀, 它是 vc-.

      知道了 VC 的前缀是 vc-, 我们可以使用 M-x apropos-command (绑定到 C-h a) 来查找所有 VC 命令:

      M-x apropos-command RET
      

      然后在提示符下输入:

      Search for a command (word list or regexp): ^vc-
      

      Emacs 返回 Apropos 搜索的结果:

      vc-annotate C-x v g
        Display the edit history of the current
        FILE using colors.
      vc-check-headers M-x ... RET
        Check if the current file has any headers in it.
      vc-clear-context M-x ... RET
        Clear all cached file properties.
      [...]
      

      你会看到一个命令列表以及简要说明和按键绑定 (如果有的话).

      快速浏览一下就会发现一些有趣的命令.

      Keys and Commands Description
      C-x v vc 的前缀键
      M-x vc-dir, C-x v d 显示 VC 状态
      M-x vc-diff, C-x v = 比较缓冲区和上一个修订版
      M-x vc-annotate, C-x v g 显示 blame
      M-x vc-next-action, C-x v v 执行下一个逻辑操作
      M-x vc-print-log, C-x v l 打印提交日志

      通过这些, 很容易看出一个趋势. 许多命令都绑定到前缀键 C-x v. 下一步是查看哪些命令绑定到前缀键本身, 方法是附加 C-h.

  3. C-h: 探索前缀键

    在" 发现和记住按键" 中, 我向你展示了当你在输入部分 (前缀) 按键时附加 C-h 会列出绑定到该前缀键的所有按键. C-x v 也不例外: 键入 C-x v C-h 会列出绑定到此此前缀键的所有按键.

    键入 C-x v C-h 会产生以下结果:

    Global Bindings Starting With C-x v:
    key             binding
    ---             -------
    C-x v +         vc-update
    C-x v =         vc-diff
    C-x v D         vc-root-diff
    C-x v G         vc-ignore
    C-x v I         vc-log-incoming
    C-x v L         vc-print-root-log
    C-x v O         vc-log-outgoing
    C-x v a         vc-update-change-log
    C-x v b         vc-switch-backend
    [...]
    

    这个命令的好处在于它非常容易输入. 如果你忘记了 C-x v = 会比较当前文件和上一个修订版怎么办? 没问题–C-x v C-h 显示绑定到 C-x v = 的是 M-x vc-diff. 另一个明显的好处是它会让你接触到你可能根本不会想到使用, 甚至不知道存在的命令. 也许你有一个新的需求, 比如说创建一个标签 (C-x v s), 如果你不确定它叫什么或者它绑定到什么–或者 Emacs 中是否存在这样的功能–那么 C-h 可能会提供一些线索.

  4. C-h k: 描述按键功能

    另一方面是有一个按键却不知道它做什么. 命令 C-h k 接受一个按键绑定, 并显示该命令在活动缓冲区中绑定到什么. 例如, C-h k 后跟 C-x v v 不仅会显示命令的名称, 还会显示该命令的文档字符串. 通常, 文本是描述性的, 并解释了该命令的作用:

    C-x v v runs the command vc-next-action (found
    in global-map), which is an interactive
    autoloaded compiled Lisp function in `vc.el'.
    
    It is bound to C-x v v, <menu-bar> <tools> <vc>
    <vc-next-action>.
    
    (vc-next-action VERBOSE)
    
    Do the next logical version control operation on
    the current fileset. This requires that all
    files in the current VC fileset be in the same
    state. If not, signal an error.
    ...
    

    上面显示的是按键绑定和它运行的命令. 它还显示了命令的查找位置–在本例中是在全局映射中, 因为它是一个全局键–以及包含该命令的库文件. 接下来, 列出了它占用的所有按键 (它可能有多个绑定), 然后是如果你直接从 lisp 调用该命令时的函数参数. 最后, 是描述该命令的文档字符串.

    所有这些信息都是在你调用 C-h k 时动态生成的.

    C-h k 的一个小缺点是其目标受众是 elisp 黑客而不是最终用户; 文档字符串从技术角度描述命令的工作原理, 这通常意味着解释每个参数以及与最终用户无关的其他技术细节. 但这对于有技术头脑的人来说通常不是问题, 即使你不是 lisp 开发者.

    1. Describing commands

      如果你有一个命令的名称, 例如 vc-dir, 你可以使用 C-h f, Emacs 会描述该命令的作用.

  5. C-h m: 查找模式命令

    如果你运行命令 C-x v d, 会出现一个新的缓冲区, 显示你当前缓冲区的仓库的版本状态; 其中会显示未跟踪和已修改的文件. 但是你如何与它交互呢? 你如何发现如何使用 VC 的状态缓冲区?

    答案是 C-h m, 一个描述模式的帮助命令. 它会显示你调用它时缓冲区中活动的所有主模式和辅模式的文档字符串, 以及这些主模式和辅模式特有的任何按键. 换句话说, 使用此命令来弄清楚每个主模式和辅模式的作用 (以及它们公开的按键, 如果有的话).

    因此, 在 VC 状态缓冲区中调用 C-h m 会产生大量的按键和文档:

    key             binding
    ---             -------
    C-c             Prefix Command
    TAB             vc-dir-next-directory
    C-k             vc-dir-kill-line
    RET             vc-dir-find-file
    ...
    

    从那时起, 只需单击 (使用 RET 或鼠标) 你感兴趣的每个超链接即可.

  6. M-S-x: 针对缓冲区的执行扩展命令

    另一个选项是使用 M-S-x, 这是 Emacs 28 中引入的. 不过, 它并非在所有地方都能产生很好的结果: 它依赖于当前缓冲区中活动模式绑定的按键, 以及模式作者用提示增强其代码以向 M-S-x 显示重要命令.

    对于 VC, 它在像 C-x v d 这样的专用模式中效果最好, 并且可以作为 C-h m 的补充.

6.0.2. 项目管理

历史上, Emacs 总是孤立地看待文件, 从不将其视为相关文件的集合. 尽管自 2002 年以来就存在文件集 (filesets)–Emacs 的一部分, 但却如此晦涩, 以至于连维护者都忘记了它–Emacs 大多将这个角色留给了目录局部变量43和像 Makefile 及其同类这样的外部工具. 这使得 Emacs 维护者和社区难以构建一个一致的, 处理多个文件 (例如搜索和替换, 编译等) 的叙事. 这种情况在 Emacs 23 中随着 EDE 的出现而改变, EDE 是 CEDET 的一个子集, 而 CEDET 本身是一个非常复杂的工具套件, 旨在为 Emacs 提供类似 IDE 的功能.

  1. What is CEDET?

    CEDET 是一组工具, 提供语言解析, 语义分析和代码补全; 项目管理; 代码生成; 图表绘制等等. 它与 C 和 C++ 配合良好, 并在该社区中拥有大量追随者, 但从未真正在其他地方流行起来. 一个删节版包含在 Emacs 23 中.

    与 CEDET 类似, EDE 从未真正流行起来, 与此同时, 大量的第三方库 (如 Projectile) 随着多语言项目的日益普及而蓬勃发展. 它们还改变了项目管理的概念, 将其建立在源代码控制仓库的存在之上, 而不是你选择性添加或删除文件的静态项目文件.

    从那时起, Emacs 引入了 project.el, 并且此后一直在积极开发中. 这个" 新的" 项目管理器功能丰富, 并且从第三方工具的集体工作中汲取了灵感, 构建出我认为对大多数用户来说都是开箱即用的绝佳体验.

    一些功能亮点包括:

    • 从 VC 索引的文件中推断文件: Emacs 会自动猜测与项目相关的文件, 前提是如果该文件被你正在使用的版本控制系统索引了, 那么它可能与项目用户相关.
    • 从 VC 主目录推断根目录: 很重要, 因为文件搜索, linting 工具, 编译命令等通常相对于根目录执行.
    • 内置支持枚举项目文件: 用于在项目的多个文件或项目文件的子集中进行搜索和替换.
    • 支持与项目一起使用的命令选择: 例如打开 M-x shell 或 M-x dired 缓冲区; 查找文件; 终止与项目相关的缓冲区; 或按名称切换到另一个项目.

    尽管我在这里列出的细节可能看起来很低级, 但它们很重要: 这是 Emacs 中一个 sorely-missed 的功能, 并且打下良好的基础–他们已经做到了–对于现在在更广泛的社区中得到采用至关重要.

    如果你还没有强烈的偏好, 你应该使用这个. 为此, 你只需要一个 Git 或 Mercurial 仓库.

    有一个专门为绑定到 C-x p 的项目保留的前缀键映射 (在 Emacs 28 中引入):

    Project Keys Description
    C-x p p 切换到其他已知项目
    C-x p b 切换到缓冲区
    C-x p f 在项目中查找文件44
    C-x p k Kill 项目缓冲区
    C-x p g 按 regexp 搜索
    C-x p r 查询 regexp 搜索和替换
    C-x p c 编译项目
    C-x p v 打开 VC 对话框
    C-x p s 打开一个 Shell
    C-x p d 打开一个 Dired 缓冲区

6.0.3. Xref: Emacs 中的交叉引用

Xref 是一个统一的平台, 用于将符号 (例如光标下的符号) 与其定义进行匹配, 然后将其呈现给用户,以便他们可以编辑或跳转到它们. 它通过可配置的后端来完成此操作,这些后端可能会根据你执行它的缓冲区的​​主模式而改变. 它旨在与第三方软件包一起使用, 尽管尚未广泛采用, 并且工具会生成自己的符号定义列表.

你应该了解的四个最常见的命令是:

Keys Description
M-. 查找光标处的定义
M-, 弹出标记45并返回
M-? 查找匹配模式的引用
C-M-. 查找匹配模式的符号
  1. Popping the Stack

    Emacs 中的一个常用术语是从堆栈中弹出 (pop) 某些东西, 指的是计算机科学中从列表中删除最近添加的项目的概念. 你会在这里和那里在 Emacs 面向用户的命令中看到它, 就像这个一样.

    M-? 按键绑定可能会回退到使用常见的​​文件搜索工具 (如 grep) 搜索你的项目目录 (如果那里没有其他后端可用). 通常, 这是一个不错的起点.

    结果显示在一个 xref 定义缓冲区中.

    Xref Buffer Keys Description
    RET 跳转到定义
    TAB 跳转到定义, 并隐藏 xref
    C-o 显示定义
    . 和 , 向上或向下导航
    r 查询搜索和替换 regexp

    尝试使用 . 和 , 快速浏览匹配列表以找到你需要的定义. Emacs 会在你浏览列表时跳转到匹配项. 一旦你对选择满意, 你可以按 TAB 跳转到光标处的定义并关闭 xref 缓冲区.

    键入 r, 系统会提示你输入要搜索和替换的 regexp. 它的工作方式与我在" 搜索和替换" 中谈到的 C-M-% 相同.

  2. Xref and Dired

    Xref 在 Dired 中也可用: 你可以键入 A 来搜索, 键入 Q 来在标记的文件中搜索和替换.

6.0.4. Working with Log Files

仔细研究日志文件是一项常见的活动, Emacs 中有一些工具可以让你轻松掌握它们.

Keys Description
C-x C-f 查找文件
C-x C-r 以只读模式查找文件
C-x C-q 切换只读模式

打开日志文件是第一步. 你可能想以只读方式打开它; 如果文件不可写, Emacs 会自动以 M-x read-only-mode 打开它. 你可以用 C-x C-q 来打开和关闭它. 你可能想要禁用只读模式的原因是, 这样你就可以对缓冲区应用破坏性更改, 例如刷新或保留行:

Keys Description
M-x flush-lines 刷新与模式匹配的行
M-x keep-lines 仅保留与模式匹配的行
M-s o 列出与模式匹配的行

M-s o 会创建一个与模式匹配的 Occur 模式缓冲区. 许多人从未考虑过的是, 你可以在 Occur 模式缓冲区上重新运行 M-s o 以进一步过滤它.

如果你滚动浏览一行又一行几乎相同的日志条目, 很容易出现模式盲点并错过一些东西. Emacs 的高亮器在这里效果很好, 因为它们会用不同的颜色高亮显示缓冲区中的模式, 以便你可以区分它们:

Keys Description
M-s h p 高亮显示一个短语
M-s h r 高亮显示一个正则表达式
M-s h . 高亮显示光标处的符号
M-s h u 移除光标下的高亮

高亮器效果很好, 并且它们在 Emacs 中随处可用. 即使你不记住这些按键, 只要知道它们都以 highlight- 命名, 因此在需要时用 M-x 执行它们也很容易.

日志文件很少是静态文件: 它们会不断更改或追加内容. 你可以启用一个辅模式, 以便在文件系统上的文件发生更改时 Emacs 刷新该文件. 在较新版本的 Emacs 上, 它会使用文件更改事件 (在 Windows 和 Linux 上) 和在不支持通知的旧系统上轮询.

Keys Description
M-x auto-revert-mode 文件更改时还原缓冲区
M-x auto-revert-tail-mode 文件更改时追加更改

这两种模式是相似的. 如果文件内容经常更改, M-x auto-revert-mode 至关重要. Emacs 会检测更改并简单地重新加载整个文件. 另一方面, M-x auto-revert-tail-mode 的工作方式与 tail -f 相同: 当文件更改时, 更改会追加到缓冲区的末尾, Emacs 会相应地滚动.

  1. Browsing Other Files

    当然, 没有什么能阻止你将这些概念应用于其他文件类型. 例如, Emacs 自带 auto compression mode–一个默认启用的被动模式–它会在你打开和保存文件时自动解压缩和重新压缩文件. 将其与 M-x dired 结合使用, 你就可以像浏览目录一样浏览压缩存档.

    1. Dired: Thumbnail Image Browser

      你可以键入 M-x image-dired 进入 Emacs 的图像缩略图浏览器. 你会看到一个包含缩略图的窗口和一个包含文件的 dired 窗口. 缩略图缓冲区带有大量的按键绑定. 以下是其中一些:

      Thumbnail Keys Description
      C-f, C-b 移动到下一个或上一个缩略图
      C-p, C-n 上下移动一行缩略图
      d, m, u 删除, 标记或取消标记 (类似 Dired)
      t t, t u 标记或取消标记图像
      RET 打开图像
      l, r 向左或向右旋转缩略图

      全尺寸图像缓冲区提供三个按键绑定:

      Image Keys Description
      q 退出
      s 将图像大小调整到窗口大小
      f 以完整尺寸显示

      不过请注意, 从 dired 打开文件会将你置于 Image Mode, 这是一个通用的图像查看器 (功能更强大), 而不是 Dired Image Display Mode.

    2. DocView: Viewing Rich Documents

      如果你的系统相对较新, 并且安装了正确的程序47, 你就可以利用 Emacs 渲染图像并在打开时动态转换 PDF 和 Open / MS Office 文档的功能.

      1. DocView Support

        要获取 Emacs 将尝试转换的完整列表, 你可以键入 C-h v auto-mode-alist 并搜索 doc-view. 你可能还记得本书前面在讨论" 主模式加载顺序" 时提到过该变量.

        要使用 DocView, 请打开一个 PDF 或其他受支持的二进制文件, Emacs 会尝试为你渲染它. 打开后, 你可以使用以下一些按键绑定:

        Keys Description
        n, p 上一页/下一页
        <prior>, <next> 上一页/下一页
        SPC 向下滚动
        S-SPC 向上滚动
        M-<, M-> 跳转到第一页/最后一页
        +, -, 0 放大, 缩小或重置
        W, H, P, F 适合宽度, 高度, 页面或窗口

        这只是可用按键绑定的一小部分. 值得注意的是 SPC 和 S-SPC, 因为它们会缓慢地浏览文档, 并且是为阅读文档而设计的. 使用 + 和 - 进行放大或缩小. 你也可以让 Emacs 通过 W 或 H 来调整文档以适应窗口的宽度或高度,從而省去猜测的麻烦. 另一方面, F 会调整你的窗口以适应文档.

        如果图像看起来像素化, 你可以通过自定义 doc-view-resolution 来增加 DPI.

      2. Slide Show Viewer

        命令 M-x doc-view-presentation 会将 Doc View 缓冲区转换为全屏幻灯片演示. 你可以用 n 和 p 在" 幻灯片" 之间导航, 用 q 退出.

6.0.5. TRAMP: 远程文件编辑

远程文件编辑通常很笨拙: 你必须与远程环境交互, 通常使用终端模拟器, 而且几乎总是没有图形界面的保真度和你的常用设置. 即使将 .emacs.d 随身携带很简单, 它仍然很笨拙. 尽管技术不断进步, 远程文件编辑通常仍涉及权衡取舍.

Emacs 的 TRAMP48 系统是一个透明的代理, 旨在解决你可能遇到的大多数远程文件交互问题. 毫无疑问, TRAMP 是 Emacs 中最酷的功能.

  1. Microsoft Windows

    在 Windows 上, 你的协议选择有所不同. 如果你不使用 Cygwin 或 OpenSSH 的交叉编译版本, 你将需要安装 PuTTY 的 plink.exe 工具并使用 plink 作为协议.

    尽管与 25 年前相比, 如今的服务器环境更加同质化, 但 TRAMP 在幕后做了大量工作, 以确保远程 shell 提供一致 (且可靠) 的体验. 我上面提到的变量 tramp-methods 控制 TRAMP 如何处理每种协议类型. 如果你使用晦涩的系统, 你可能需要自定义此变量.

    TRAMP 的另一个 nifty 功能是它会解析你的 ~/.ssh/config 文件并自动补全已知主机 (当你输入 ssh 作为协议时). 当然, 你也可以手动指定主机名, 用户名和端口, 其中后两者是可选的.

    1. SSH config

      如果你使用 ssh, 我强烈建议你使用配置文件, 因为你可以将所有连接和凭据详细信息存储在一个易于记住的名称中. 有关更多信息, 请键入 M-x man RET sshconfig 以在 Emacs 中阅读相关手册页.

      最后, 要实际调用 TRAMP, 你必须从根目录调用它–在 FIDO 或 IDO 模式下键入 // 会跳转到根目录–并遵循上面的格式.

    2. Helm

      如果你也使用 Helm, 你可以安装一个名为 helm-tramp 的第三方包, 它可以自动生成你可能想使用的所有合理的 TRAMP 配置. 你还可以找到添加对 Docker 等工具支持的 TRAMP 扩展.

      请注意, Emacs 在你输入第二个 : 之前不会启动远程连接, 如下所示:

      /ssh:homer@powerplant:/var/log/reactor.log
      

      上面的命令使用 ssh 作为协议, homer 作为用户连接到服务器 powerplant. 然后它会打开文件 /var/log/reactor.log.

  • The Default Directory and Remote Editing

    每个缓冲区都有一个 default-directory 变量. 从 elisp 的角度来看, 该变量是缓冲区局部 (buffer local) 的. 每个缓冲区都有其自己的 default-directory 变量, 因为它仅对该缓冲区局部有效, 而不是全局的 (像 Emacs 中默认的变量那样).

    当你在缓冲区中键入 C-x C-f 时, Emacs 会查找 default-directory 并选择该目录作为打开新文件的默认目录. 这是合理的, 因为你可能想打开与当前缓冲区共享目录的其他文件. 在 /etc 中编辑文件时键入 C-x C-f 意味着你可能想在 /etc 中打开另一个文件, 因此 Emacs 会选择它作为你的默认目录.

    此功能与 TRAMP 和远程文件完全相同. 在远程编辑的文件中调用 C-x C-f, Emacs 会自动查询远程系统而不是你的本地系统, 从而让你轻松打开其他远程文件.

    当你打开一个文件后, TRAMP 会在幕后施展魔法, 你最终会在 Emacs 中得到一个看起来和感觉上都像本地文件的文件. 判断一个文件是否是远程文件的唯一可见方式是模式行: 文件名前面会出现一个 @, 并且 default-directory 会反映 TRAMP 注释的文件路径; 试试看, 用 C-h v 检查该变量.

    所以, 你可以远程编辑文件, 但由于 TRAMP 和 Emacs 之间的紧密集成, 你可以做的远不止这些. 调用像 M-x rgrep 这样的命令可以与 Emacs 和 TRAMP 无缝协作. 该命令在远程计算机上运行, 结果会反馈给 Emacs, 就好像你在本地调用该命令一样.

    你可以远程调用的东西无穷无尽. 以下是我远程使用的一些命令:

    • C-x d: Dired 所有命令都通过远程会话进行隧道传输, 因此你可以像管理本地文件和目录一样使用 dired 管理它们. 你甚至可以在远程和本地 dired 会话之间复制文件, TRAMP 会透明地复制文件.
    • M-x compile: Compile 你可以输入一个编译命令, 例如 make 或 python manage.py runserver, 或者实际上任何你喜欢的东西. Emacs 会远程运行该命令, 输出会显示在 compilation 缓冲区中. 你甚至可以远程运行交互式服务器并获得实时反馈.
    • M-x rgrep: Grep Commands find 和 grep 都会被远程调用, 并且与其他命令一样, 结果会显示在 Emacs 中. grep 输出中的超链接文件会正确打开远程文件.
    • M-x shell: Emacs' s Shell Wrapper 启动一个远程登录 shell 并将控制权交给你. 它的工作方式与本地计算机上的 M-x shell 完全一样, 但本例中的 shell 显然在远程计算机上. TAB 补全–在 M-x shell 中由 Emacs 而非实际 shell 完成–也有效.
    • M-x eshell: EShell, Emacs' s elisp shell Eshell 是一个用 Emacs lisp 编写的 shell. 它也透明地与远程 TRAMP 连接一起工作. 事实上, 你可以直接从本地 shell `cd` 到远程目录.
  • Multi-Hops and User Switching

    TRAMP 的另一个很酷的功能是使用 su 或 sudo 进行帐户提升. 这甚至适用于本地文件, 如果你想以其他用户或 root身份编辑文件.

    执行此操作的能力也与多跳 (multi-hops) 的概念巧妙地联系在一起: 通过中间主机连接到远程主机. 一个例子是, 如果你必须通过公共服务器连接到内部服务器以增加安全性; 另一个例子是, 如果你必须以一个用户身份登录, 但随后必须调用 sudo 以 root 身份在远程服务器上编辑文件.

    让我们从请求 sudo 访问 /etc/fstab 的更简单情况开始:

    /sudo:root@localhost:/etc/fstab
    

    如你所见, 语法与正常的远程 TRAMP 连接完全相同–只是我们使用的是 sudo, 并且我们正在本地连接. 你通常可以省略 root@, 因为 TRAMP 足够聪明, 可以猜到它是 root. 请记住, 此文件在技术上是远程的 (在 TRAMP 的意义上), 因此通常的 default-directory 规则适用. 在远程缓冲区中使用 C-x C-f 打开文件将以 sudo 身份打开其他文件.

    TRAMP 中的多跳通常通过自定义 tramp-default-proxies-alist 来完成, 但我发现它有点繁琐; ad hoc 语法要容易得多:

    /ssh:homer@powerplant|sudo:powerplant:/root/salary.txt
    

    上面的示例以 homer 身份连接到 powerplant. 然后, 另一个" 连接" 调用 sudo 并以 sudo 用户身份打开 /root/salary.txt. 重复 sudo 字符串中的主机名非常重要, 否则它将无法工作. 与以前一样, 远程文件遵循与之前相同的规则. 如果从 salary 文件调用, 像 M-x shell 这样的命令会为你提供 powerplant 上的 root shell.

    1. Bookmarks

      你可以用 C-x r m 为远程文件添加书签 (参见" 书签和寄存器" ), 如果你稍后用 C-x r b 或 C-x r l 重新打开书签, TRAMP 会自动重新连接. 书签适用于 Emacs 的许多功能. 在这里, 它们也是一个巨大的节省时间的方法, 特别是对于复杂的多跳.

      最后, 我建议你将此代码片段添加到你的 init file 中. 这是一个自定义命令, 当作为 M-x sudo 调用时, 它会使用 TRAMP 以 root 身份编辑当前文件:

      (defun sudo ()
        "Use TRAMP to `sudo' the current buffer"
        (interactive)
        (when buffer-file-name
          (find-alternate-file
           (concat "/sudo:root@localhost:"
                   buffer-file-name))))
      

      从上面可以看出, 很容易调整字符串并构建多跳命令–如果你是 elisp 新手并且需要多跳, 可以将其视为一个有趣的起点.

    2. Conclusion

      TRAMP, 与 Emacs 内置的 shell 支持及其窗口和缓冲区相结合, 使其成为 tmux 和 GNU screen 式工作流程的绝佳替代品. 通过将远程文件编辑保留在 Emacs 内部, 你统一了你的环境, 并极大地减少了拥有不同 Emacs 会话所带来的心智上下文切换. TRAMP 是 Emacs 中一个非常强大的功能, 值得在其他替代方案之上使用–它永远不会完全取代现有的远程编辑方法, 但它是一个很好的起点.

  • 6.0.6. EWW: Emacs Web Wowser

    很久以前, Emacs 就屈服于 Zawinski 的软件膨胀定律, 该定律指出:" 每个程序都试图扩展直到它可以阅读邮件. 那些无法如此扩展的程序会被可以的程序所取代" , Emacs 内置了不止一个, 而是许多邮件客户端.

    显然, 对于二十一世纪, 我们需要提高标准. 因此, Emacs 内置了一个 Web 浏览器 EWW–Emacs Web Wowser, 这不足为奇.

    EWW 是一个精巧的小型浏览器引擎. 它的渲染器仅限于基本的 HTML 和图像支持, 因此与现有的基于文本的浏览器没有太大区别. 然而, 尽管其渲染器有限, 但它非常适合细读技术文档, 快速进行 Web 搜索以及浏览你不想让老板看到的网站.

    要使用 EWW, 只需键入 M-x eww, 系统会提示你输入 URL 或搜索词. 默认情况下, 搜索引擎是 DuckDuckGo, 但你可以自定义 eww-search-prefix 并将其更改为其他内容. 从 Emacs 27 开始, 键入 C-u M-x eww 会创建一个新的 EWW 缓冲区, 即使已经存在一个.

    1. External and internal browsing

      你可以指示 Emacs 在 Web 浏览器中打开内容, 命令是 M-x browse-url. Emacs 会使用变量 browse-url-default-browser, 这是一个会尝试根据你的操作系统和已安装的浏览器来猜测正确浏览器的函数. 你可以通过键入 M-x customize-option browse-url-default-browser 来自定义默认浏览器.

      EWW 中的导航主要通过键盘进行 (正如你对 Emacs 的期望), 但超链接可以用鼠标单击.

      Keys Description
      TAB, S-TAB 循环到下一个/上一个超链接
      q 退出 EWW
      & 用 M-x browse-url 打开页面
      B 显示书签
      b 添加书签
      H 显示浏览器历史
      l, r 向后/向前浏览历史
      p, n, u, t 语义 Web 导航辅助
      R 启用阅读器模式
      RET 浏览链接
      C-u RET 在外部浏览器中打开链接
      M-s M-w 在 EWW 中搜索光标处的内容
      M-RET 在新缓冲区中打开链接
      s 切换到另一个 EWW 缓冲区
      w 复制光标处的链接
      1. Emacs 28

        在 Emacs 28 中, EWW 现在与 Emacs 的其余部分共享一个书签系统.

        最常用的命令是 TAB 和 S-TAB 来打开超链接; 以及 l 和 r, 因为它们是你用来在浏览器历史记录中前进和后退的命令. 命令 p, n, u 和 t 仅在网页语义上说明哪些超链接是下一个和上一个时才起作用.

        你可以用 R 切换到阅读器模式 (reader mode). 它很棒. 它会去除–或者尝试去除, 但 EWW 大部分时间都能正确处理–所有干扰, 只留下文本的主要内容. 非常适合阅读.

        命令 M-s M-w 也很有用. 它会获取活动区域并将其发送到默认搜索引擎 (通常是 DuckDuckGo) 并在 EWW 中显示结果.

    6.0.7. Dired: 文件和目录

    在文件系统上浏览和交互文件及目录是 Emacs 非常擅长的一项任务. 除了以通常的方式编辑本地文件和使用 TRAMP 编辑远程文件外, 你还可以使用 Emacs 的目录编辑器 dired 来操作目录和文件.

    要访问 dired, 你可以通过多种方式执行此操作:

    • 从 IDO 或 FIDO 模式: 当你使用 C-x C-f 查找文件时, 可以键入 C-d 以在该文件的当前目录中打开一个 dired 缓冲区.
    • 作为命令: 命令 M-x dired 会打开一个提示, 要求你输入要打开的 dired 位置. 它默认为 default-directory, 即当前缓冲区所在的目录. 与 TRAMP 一样, 如果文件是远程的, Emacs 会询问你是否要进行远程 dired 会话.
    • 作为按键绑定: 按键绑定 C-x d 的功能与上面的命令完全相同. 命令 C-x 4 d 的功能相同, 但在其他窗口中执行.

    当你在 Emacs 中打开一个 dired 缓冲区时, 你会看到一个类似于这样的视图:

    /usr/share/dict:
    total used in directory 2328 available 187646744
    drwxr-xr-x   2 root root  4096 Feb 16 09:57 .
    drwxr-xr-x 326 root root 12288 Mar 27 11:43 ..
    -rw-r--r--   1 root root 938848 Oct 23  2011 american-english
    -rw-r--r--   1 root root 938969 Oct 23  2011 british-english
    -rw-r--r--   1 root root    199 Jan 14  2014 select-wordlist
    

    如果你使用 Linux 命令行, 它的输出应该看起来很熟悉. 这是因为 Emacs, 秉承像 M-x grep 这样的其他命令的精神, 只是增强了现有命令行实用程序的输出. 在本例中, 它通常是 ls -al, 但你可以通过自定义 dired-listing-switches 来更改使用的开关.

    1. Microsoft Windows

      如果你使用 Microsoft Windows, 别担心. Emacs 包含一个用 elisp 编写的 ls 仿真层. Emacs 不会调用 ls, 而是直接查询操作系统. 最终结果是一个跨平台无缝工作的界面.

      正如我之前谈到的, The Buffer 的概念和 Emacs 的" 增强" 系统是与外部程序通信的一种强大而实用的方式. 当 Emacs 调用 ls 时, 输出会插入到缓冲区中, dired-mode 会被激活, 并且文本会通过高亮, 超链接和其他隐藏属性得到增强, 以帮助 Emacs 机械地导航文本. 主模式本身提供按键绑定, 以便在文件上按 RET 会打开它. 实际上, 与任何缓冲区一样, 你可以复制 dired 的输出, 因为它基本上是纯文本.

      大多数 Emacs 初学者–甚至中级用户–从未真正掌握 dired. 大多数人从未超越用它导航目录的程度, 这很可惜, 因为在其简单的外表之下是一个非常复杂和高效的文件和目录操作系统. 事实上, 有数百个 dired 命令和数十个与 dired 界面无缝协作的其他命令的交互.

  • Navigation

    导航 dired 相当简单, 由于它是一个缓冲区, 你所有常用的导航辅助工具都有效: Isearch, 箭头键, 鼠标等等.

    Keys Description
    RET 访问文件或目录
    ^ 上一级目录
    q 退出 dired
    n, p, C-n, C-p 将光标在列表中上下移动

    然而, 如果你想进入当前目录的父目录, ^ 是你需要的键. 命令 C-n 和 n 以及 C-p 和 p 会向下或向上移动一行, 但也会重新定位你的光标, 使其位于文件名前面.

    当你按 RET 时, Emacs 会访问该文件或目录; 如果是目录, 则会打开一个新的 dired 缓冲区. 因此, 在访问子目录后按 q 应该会将你带回上一个 dired 缓冲区.

  • Marking and Unmarking

    如果你想对多个文件或目录执行操作, 标记和取消标记是你经常会做的事情.

    Keys Description
    m 标记活动项
    u 取消标记活动项
    U 取消标记所有项
    d 标记为删除

    标记和标记为删除之间必须做出重要区分: d 会标记为删除 (并在标记的项目旁边放置一个 D), 而 m 会进行标记. 标记永远不会受删除命令的影响, 反之亦然, 除了一个删除标记文件的命令. 标记的文件会用 * 高亮显示.

    标记和标记为删除都会将光标移动到下一个项目 (就好像你键入了 C-n), 但你可以用负参数反转方向.

    1. Discover more

      dired 中的命令太多了, 不可能全部列出. 我建议你应用通常的探索方法 (Apropos, 描述模式, 列出绑定到前缀的键) 来发现其余内容. 或者, 你可以尝试我的包 Discover, 它向 Emacs 添加了描述性弹出菜单. 你也可以浏览我的网站, 获取详细介绍各种 dired 功能的文章.

      还有一些标记特定内容的标记命令:

      Keys Description
      * m 标记区域
      * u 取消标记区域
      * % 按 regexp 标记文件
      * . 按扩展名标记文件
      t, * t 切换标记
      * c 更改标记

      前缀键 * 充满了标记命令. 上面显示的是日常使用中最实用的四个. 区域键会标记或取消标记活动区域接触到的每个 dired 项目. regexp 和扩展名标记命令类似有用, 你可以使用 * t 来切换 (反转) 标记.

      • c 很特别. 它会将标记符号从旧的更改为新的. 因此, 你可以将 * (默认标记符号) 更改为 D, 并将标记的文件转换为标记为删除的文件. 然而, 由于删除几乎是你唯一想对标记为删除的文件执行的操作, 因此有一个专门的命令用于删除标记为删除的文件, 另一个命令也用于删除标记的文件.
  • Operations

    你可以对活动项 (如果 dired 中没有标记的文件) 或标记的项 (如果有) 执行操作.

    当你对标记的文件执行操作时, Emacs 通常会要求你确认操作, 并列出受影响的文件. 与标记命令一样, 你可以执行许多操作. 让我们先看看基本操作:

    Keys (Marked files) Description
    C 复制文件
    R 重命名或移动文件
    O 更改所有者
    G 更改组
    M 更改权限
    D 删除标记的文件 (显示为 *)
    x 删除标记为删除的文件 (显示为 D)
    F 访问文件 (需要 dired-x)
    c 将标记的文件压缩成一个文件

    上面的大多数按键都是不言自明的. 如果要删除文件, 只需记住 x 和 D 之间的区别即可.

    1. Copying or renaming between dired buffers

      如果你自定义了选项 dired-dwim-target, 你可以在两个带有 dired 缓冲区的窗口之间复制或重命名 (移动) 文件. 小心不要意外地将文件移动到你忘记打开的错误 dired 缓冲区–我自己就犯过好几次这样的错误!

      还有一些不专门作用于标记文件的按键:

      Keys Description
      g 刷新 dired 缓冲区
      + 创建一个子目录
      s 按名称/日期切换排序
      <, > 跳转到上一个/下一个目录
      j 跳转到文件
    2. Dired-X

      有些命令需要 dired-x. 这是一个由于某种原因默认未启用的包, 不幸的是, 你必须手动启用它. 将此添加到你的 init file 中以使其生效:

      (require 'dired-x)
      

      安装 dired-x 后, 你可以使用 F, 它会访问所有标记的文件. 重要的是, 它会尝试打开文件并为每个文件提供自己的窗口–这可能不是你想要的. 为了避免这种情况并在后台打开它们, 请键入 C-u F.

      Keys (Marked files) Description
      M-s a C-s 开始 isearch
      Q Xref 查询替换 regexp
      A Xref 按 regexp 搜索
      ! 同步 shell 命令
      & 异步 shell 命令

      有时, 你需要在文件中搜索或替换文本, 你可以使用相当笨拙的按键绑定 M-s a C-s 来进行多文件 Isearch. 命令 Q 会在每个标记的文件上调用 C-M-% – query replace regexp. 但不要忘记保存更改 (C-x s 会查询以保存每个未保存的缓冲区).

      如果在启用了 dired-x 的情况下调用 ! 而没有标记, dired 会尝试猜测该文件上的下一个操作: 如果是 .zip 文件, 它会询问你是否要解压缩它. 如果是 .patch 文件, Emacs 会在其上调用 patch. 变量 dired-guess-shell-alist-default 中指定了许多模式. 这是一个巨大的节省时间的方法.

      如果存在活动标记, 那么 ! 和 & 将改为调用你选择的外部命令. 你还必须告诉 Emacs 如何将标记的项目传递给该命令:

      • 每个标记项一个命令: 在此模式下, Emacs 会为你选择的命令针对每个标记项调用一次.
      • 所有标记项一个命令: 在此模式下, Emacs 会将每个标记项用空格分隔并将其传递给单个命令.

      由于某些命令可能需要很长时间才能运行, 因此你必须决定是否希望 Emacs 同步调用该命令, 从而阻塞 Emacs 直到完成 (使用 !); 或者, 你可以告诉 Emacs 异步生成该命令 (使用 &).

      这是一个实际的例子. 考虑这两个文件: american-english 和 british-english, 根据你如何表述 shell 命令, 行为会有所不同. 你可以选择指定 * 或 ? 来指示你想要每个标记一个命令还是所有标记一个命令.

      • - 像 shell 的文件通配符模式一样工作, Emacs 会将所有标记的文件作为单个长参数插入到单个命令中:

        echo *
        

        打印:

        american-english british-english
        

        因为 Emacs 对两个标记的项目都只运行了一次命令.

      • 而 ? 则会为每个标记的项目运行该命令:

        echo ?
        

        那么, 输出是:

        american-english
        british-english
        

      输出 (如果有) 会打印在回显区 (如果只有几行). 否则, 它会重定向到一个名为 Shell Command OutputAsync Shell Command 的专用缓冲区.

  • Working Across Directories

    与其在新的 dired 缓冲区中打开目录, 不如告诉它将其内容插入到现有的 dired 缓冲区中. 当你在目录上用光标键入 i 时, dired 会将其作为子目录插入到同一个 dired 缓冲区中. 这意味着你可以在同一个 dired 缓冲区中的 dired 目录之间使用相同的标记和标记命令. 你可以用 $ 折叠子目录–这意味着在折叠时命令不会应用于它.

    通过将多个目录插入到共享的 dired 缓冲区中, 你不仅可以同时浏览多个目录, 还可以像处理一个大目录一样处理它们. 这是 Emacs 中另一个强大但未被充分利用的功能.

    然而, 还有另一种方法, 因为键入 i 很繁琐, 并且如果你想递归地应用 dired 或 shell 命令, 效果不佳.

    要解决这个问题, 你可以使用 Emacs 的 find 包装器命令. 我认为这些命令, 再加上 dired 的强大功能, 几乎完全取代了所有直接使用 find 和 xargs 的情况. 借助 dired 的 shell 命令支持和广泛的文件操作, 我可以在 Emacs 中完成大多数人难以用 find 完成的事情.

    所有命令都接受 find 的输出, 并相对于起始目录构建一个 dired 缓冲区. Emacs 足够聪明, 能够注意到缓冲区文件名部分中的相对路径. dired 中的所有命令都照常工作.

    Commands Description
    find-dired 使用模式调用 find
    find-name-dired 使用 -name 调用 find
    find-grep-dired 调用 find 和 grep
    find-lisp-find-dired 使用 Emacs 和 regexp 查找文件

    前三个命令调用命令行实用程序 find. find-dired, 与 grep 命令类似, 是最基本的一个: 你必须给它一个 find 模式和一个起始目录. find-name-dired 按 shell glob 模式仅对文件名进行查找, 从你选择的特定目录开始. find-grep-dired 匹配所有文件, 但仅显示与传递给 grep 的模式匹配的文件.

    1. Microsoft Windows

      Microsoft Windows 可以选择安装像 GNUWin32 这样的交叉编译二进制文件或 Cygwin, 或者使用 find-lisp-find-dired.

      命令 find-lisp-find-dired 是 Emacs 对 find-dired 的 elisp 实现. 它适用于任何平台, 并且不需要外部工具. 作为回报, 它没有那么强大. 此外, 它使用 Emacs 的正则表达式引擎, 而不是 shell globbing.

  • 6.0.8. Shell 命令

    正如 dired 章节所演示的那样, Emacs 中有与 shell 交互的强大命令. 对于所有其他缓冲区, 有更通用但同样强大的 shell 命令适用于通用缓冲区.

    Keys Description
    M-! 调用 shell 命令并打印输出
    C-u M-! 同上, 但插入到缓冲区中
    M-& 类似 M-!, 但异步执行
    C-u M-& 类似 C-u M-!, 但异步执行
    M-\   将区域通过管道传递给 shell 命令
    C-u M-\   类似 M-\ , 但替换区域

    你可以用 M-! 调用任何 shell 命令, Emacs 会在回显区打印其输出 (如果文本只有几行); 或者, 如果你使用了 M-!, 则会打印到一个名为 Shell Command Output 的专用缓冲区, 如果你使用了 M-&, 则会打印到 Async Shell Command. 对任一命令使用通用参数会将输出插入到当前光标位置的缓冲区中.

    M-\| 命令更实用. 它将区域作为输入, 并将其发送到你选择的 shell 命令的标准输入, 然后以与 M-! 非常相似的方式返回输出: 要么在回显区, 要么在专用缓冲区中. 对该命令使用通用参数会替换活动区域; 这使得 C-u M-\| 非常适合随手调用像 uniq 或其他修改其输入的命令行工具.

    尽管 M-& 是异步的–也就是说, 它在终止之前不会阻塞 Emacs–但对于长时间运行的任务来说, 它是一个相当糟糕的选择. 最好使用 M-x compile.

    1. Compiling in Emacs

      调用 shell 命令是为了快速执行一次性命令, 通常不是你经常重复执行的操作. 为此, 你应该考虑 Emacs 的 M-x compile 命令, 尽管它的名字如此, 但它擅长的不仅仅是编译.

      Commands Description
      M-x compile 运行命令, 并跟踪错误
      M-x recompile 重新运行上一个命令
      M-g M-n, M-g M-p 跳转到下一个/上一个错误 (全局)
      g 重新运行上一个命令
      C-x p c 在当前项目中编译

      当你调用 M-x compile 时, 系统会要求你输入一个命令, Emacs 会好心地假设你正在使用 make. 然而, 你可以自由地用任何你想跟踪其输出的命令替换它: 单元测试, 编译, 运行脚本–随你怎么说.

      M-x compile 的主要优点是其辅助命令 M-x recompile, 因为它会重新运行你的上一个命令. Compile 还会 благодаря своему механизму сопоставления с образцом отслеживать ошибки. 与 M-x grep 和 M-x occur 类似, M-g M-n 和 M-g M-p 命令会在调用堆栈或编译器错误日志中跳转, 前提是其格式与 Emacs 已知的格式匹配. 从 Python 到大多数编译器, Emacs 都知道这些格式, 所以它很可能也适用于你的情况.

    2. Shells in Emacs

      与其使用外部终端模拟器–或者在终端中运行 Emacs 以便可以将其与 tmux 或 screen 一起使用–为什么不将 Emacs 用作" 多路复用器" 并改用 Emacs 来运行你的 shell 呢? 结合 TRAMP 以及 Emacs 的平铺窗口管理和缓冲区支持, 你几乎可以取代专用终端模拟器的所有常见用例.

      在 Emacs 中与 shell (例如 bash) 交互有三种方式. 一种是围绕外部现有 shell (例如 bash) 的简单包装器, 称为 M-x shell; 另一种是用 elisp 编写的完整 shell 实现, 称为 M-x eshell; 第三种是称为 M-x ansi-term 的终端模拟器.

      这三者都非常强大, 并且各自以其独特的方式尝试解决问题. 无论你使用哪一个 (你很可能最终会使用不止一个), 都会有一些权衡取舍.

      然而, 这三者都使用 elisp 要么与外部程序通信, 要么在 Emacs 中实现 shell, 要么解释呈现复杂交互式程序 (如 top) 所需的终端控制代码. 这三者也都使用 Emacs 强大的缓冲区范式–你现在应该已经很熟悉了–来为所有三种实现提供统一的界面.

      缓冲区范式在这里特别强大, 因为与外部程序或直接与操作系统通信的能力是使 Emacs 成为如此强大编辑器的部分原因. 你获得了所有的编辑和移动命令, 以及 elisp 的强大功能, 在一个同时用于更传统的文本编辑和现在更高级和专门的与 bash 交互等操作的缓冲区中. 由于这两个极端共享一个共同的基础–缓冲区–你不需要重新学习一个全新的系统; 不再需要在终端模拟器中用鼠标费力地手动选择文本只是为了将其复制到你的文本编辑器或 Web 浏览器中. 在 Emacs 中, 一切都是文本, 你熟悉的所有移动和编辑命令在这里都完全相同.

      1. M-x shell: Shell Mode

        Emacs 中的 Shell 模式会调用一个外部程序–例如 Linux 上的 bash 或 Windows 上的 cmd.exe–要么在 Windows 上重定向 stdin, stdout 和 stderr, 要么通过伪终端 (在 Linux 上), 以便你可以通过 Emacs 与底层 shell 交互.

        然而, 由于 Emacs 重定向 I/O, 你会获得随之而来的所有好处和坏处. 例如, 你不能使用 shell 的原生 TAB 补全机制. 相反, 你必须使用 Emacs 自己的 (在某些方面更强大). 硬币的另一面是 shell 模式缓冲区完全是文本: 你可以编辑和删除命令的输出, 并且可以轻松地在缓冲区之间 kill 和 yank 文本. 这使得 shell 模式灵活但两极分化. 像 top 和 man 这样的程序要么根本不起作用, 要么即使起作用, 效果也不好.49

        1. GNU readline and defaults

          大多数 Linux 发行版使用 GNU readline–一个库–来提供基本的命令提示符功能, 例如: 命令历史, 搜索和替换以及高级移动和编辑. 默认情况下, 它们可以使用 Emacs 按键绑定进行访问.

          以下是一些最常见的命令. 不幸的是, 就绑定而言, 它们到处都是.

          Keys Description
          M-p, M-n 循环浏览命令历史
          C-<up>, C-<down> 循环浏览命令历史
          M-r 反向搜索历史 (ISearches)
          C-c C-p, C-c C-n 跳转到上一个/下一个提示
          C-c C-s 将命令输出保存到文件
          C-c C-o 将命令输出 Kill 到 kill ring
          C-c C-l 列出命令历史
          C-d 删除前一个字符或发送 ^D
          C-c C-z 发送停止子作业信号
          TAB 在光标处补全

          M-p, M-n, C-<up> 和 C-<down> 都以与在普通终端模拟器中使用上下箭头键非常相似的方式在命令历史记录中循环. 不过, 在 Emacs 中, 它们实际上是在缓冲区中移动光标, 这总是让不习惯 shell 模式的人感到困惑.

          M-r 会触发历史记录反向 Isearch. 这是一个非常强大的命令, 值得学习. C-d 会删除光标前的字符, 就像在其他任何地方一样. 但是, 如果没有输入 (意味着你没有在提示符下键入任何内容), Emacs 会发送控制代码 EOF 来终止正在运行的程序. 类似地, C-c C-z 的作用与 bash 中的 C-z 对作业控制的作用相同.

          shell 模式的一个 nifty 功能是能够用 C-c C-s 将上一个命令的输出保存到文件, 并用 C-c C-o 将其直接发送到你的 kill ring.

          TAB 值得特别提及. 像 bash 这样的 shell 具有自己复杂的补全机制, 不仅仅是文件和路径. Emacs 也是如此. 你可以补全像 ssh 这样的命令的主机名, 或者 chown 的组和所有者.

      2. M-x ansi-term: Terminal Emulator

        Emacs 有自己的 ANSI 兼容终端模拟器. 调用 M-x ansi-term 并选择一个 shell, 你就可以运行像 top 甚至 vim 和 emacs 这样的交互式程序. 它的主要缺点是速度慢, 并且不支持一些晦涩的终端仿真功能.

        Keys Description
        C-c C-j 切换到行模式
        C-c C-k 切换到字符模式

        默认情况下, ansi-term 的行为类似于常规终端模拟器, 而不像 shell 模式或典型的 Emacs 缓冲区. 但是, 你可以在两种不同的模式之间切换: 行模式 (line mode), 它类似于典型的 Emacs 缓冲区; 以及字符模式 (character mode), 它类似于普通的终端模拟器.

        默认模式是字符模式, 这意味着大多数按键–包括键盘字符, 而不仅仅是 Emacs 按键绑定–都会直接发送到底层 shell 程序, 完全绕过 Emacs. 有一个转义字符 C-c, Emacs 会拦截它, 以便像 C-c C-j 和 C-c C-k 这样的命令不会发送到子程序. 因此, 如果你想向子程序发送 C-c, 你必须键入 C-c C-c.

        如果你想要 Emacs 中最忠实的终端体验, ANSI term 是你的最佳选择. 我发现行模式和字符模式之间的切换相当麻烦, 所以我更喜欢改用 shell 模式.

      3. M-x eshell: Emacs' s Shell

        有人用 elisp 写了一个完整的 shell, 这不足为奇. 当你运行 M-x eshell 时, 你使用的是一个用 elisp 编写的 shell, 它通过 Emacs 与底层主机操作系统通信, 并提供了对典型 Linux 风格 bash shell 的出色模拟, 还包括用 elisp 模拟的 GNU coreutils 命令, 如 ls, cp, cd 等等.

        在实践中, 这意味着你在 Emacs 运行的所有平台上都能获得一致的 shell. 结合原生的 TRAMP 支持以及将命令输出直接重定向到 Emacs 缓冲区的功能, 你就拥有了一个多功能, 强大且非常符合 Emacs 精神的工具.

        Eshell 更像是 shell 模式而不是 ANSI term. 它不支持像 top 这样的交互式程序, 而是更倾向于在你从 Eshell 调用它们时打开一个专用的 M-x ansi-term 实例来运行这些程序–这是一个聪明而实用的解决方案.

        另一个重要的区别是, 尽管 Eshell 的灵感来自像 bash 这样的 shell, 但它实际上是它自己的 shell 实现, 具有所有随之而来的怪癖, 特性和限制. 必须说, Eshell 首先是一个 elisp shell, 因为你键入到 Eshell 中的每个命令首先会通过 Eshell 自己的仿真层进行过滤, 然后通过 Emacs 自己的交互式命令, 最后通过你的 $PATH 或当前目录中的程序. 例如, 你可以键入 dired . 在当前目录中打开一个 M-x dired 会话, 或者键入 find-file todo.org 在你当前运行的 Emacs 中打开 todo.org.

    7. 第七章 结论

    " Emacs 是基础. 我们在上面跑来跑去, 做些傻事, 当我们死去时, 愿我们的残骸为它持续的增量添砖加瓦." – Thien-Thi Nguyen, comp.emacs.

    你如何掌握像 Emacs 这样多样化的文本编辑器?

    令人惊讶的是, 答案很简单: 通过知道如何向它提出正确的问题. 正如我在" Emacs 即操作系统" 中谈到的, Emacs 的本质是可以通过 elisp 修改和扩展的. 因此, 真正理解 Emacs 中发生的事情的唯一方法就是询问它–简单, 但真实. 而询问 Emacs 是所有 Emacs 大师都会做的事情. 无论是检查一个键绑定到什么, 还是一个命令具体做什么, 这都是定义 Emacs 精通的一部分. 是的, elisp 的知识有很大帮助, 但并非绝对必要.

    在本书中, 我写了关于特性和功能以及我自己对哪些值得关注, 哪些不值得关注的个人看法. 这是本书真正实用的, 首要的方面. 更深层的教训–也是我刚开始学习 Emacs 时最终的转折点–是理解如何向 Emacs 提问.

    不记得一个按键或一个命令是完全正常的, 特别是当你还在学习的时候, 但是知道 Emacs 可以告诉你它做什么, 即使你已经大量修改或更改了你的按键绑定, 这才是最终帮助你真正掌握 Emacs 的关键. 忘记 C-x r l 做什么无关紧要, 当你可以使用 C-h k 找出答案时; 部分记住某些东西的作用也不重要, 当你可以将 C-h 附加到任何前缀键来描述绑定到它的所有按键时.

    任何 Emacs 用户的长期目标是达到一个可以通过询问 Emacs 来寻求问题答案的程度. 最终, 你会将最常用的命令和按键牢记于心形成肌肉记忆, 其余的, 嗯, 你总是可以查阅它们.

    使用 Emacs 足够长的时间–已经达到这一点的你们可能会同意我的观点–总有一天它会豁然开朗. 而当它发生时, 并非因为你设法记住了上千个按键绑定. 而是因为 Emacs 不再是一个不透明的盒子, 而是一个非常开放和透明的盒子, 你可以窥视, 修改并观察这些更改的结果.

    本书的阅读顺序与我教别人 Emacs 时 (如果他们坐在我旁边) 的方式相同. 理解术语很重要, 因为它奠定了基础; 接下来是最基本的按键和命令, 以便你可以使用 Emacs; 然后是移动和编辑命令, 再加上一些实际示例来帮助巩固所学知识, 并为你提供一些关于从哪里开始的想法.

    最后, 我想谈谈一旦你觉得从这本书中学不到更多东西后应该做什么. 自然的下一步是学习 elisp; 这是一种有趣的语言, 即使 LISP 方言缺乏更现代 LISP 的许多花哨功能. 学习 LISP, 你会欣赏为什么那些固执的老前辈们将" 现代" 编程语言斥为 LISP 的劣质版本–他们也说对了一半. 一旦你看到 LISP 的数据即代码概念在实践中是如何运作的–并且它在 Emacs 中无处不在–你会想知道为什么你没有早点学习它.

    1. 进一步阅读

      我自己的博客 Mastering Emacs 充满了你应该接下来阅读的深度文章. 许多第三方包, 如 IDO 模式或 Emacs 的 Eshell, 在网站上有更详细的描述. https://www.masteringemacs.org/

    7.0.1. 其他资源

    1. 第三方包和工具

      我自己使用许多第三方包. 以下只是我发现最有趣的一些的简短列表.

      对于工具, 我多次推荐 ripgrep, 因为它速度很快. 它还默认支持许多不同的文件格式, 包括 Org mode.

    2. 社区

      互联网上有很多社区网站和博客. 以下是我推荐的一些不完全列表.

    Footnotes:

    4

    https://github.com/joaotavora/eglot

    这两个产品都在积极开发中, 因此变化迅速. 它们支持各种工具和语言.

    更好的 IDE-like 功能确实提升了 Emacs 的形象, 但学习 Emacs 的一个无法回避的事实是, 一旦它成为你的第二天性, 你往往会忘记学习过程中的艰辛.

    这正是非用户抓住不放的一点, 因为它肤浅且易于批评: 术语晦涩; UI 粗暴; 按键绑定复杂. 的确如此. 但这就是 Emacs: 你可以改变所有这些.

    不过我相信这些抱怨的核心是有道理的: 一个更漂亮的 UI 是速效药; 改变 Emacs 一些更晦涩的默认设置是另一回事, 尽管这可能会惹恼一些固执的老古董.

    但第一印象很重要: 人们会根据外表和形式做出快速判断; 如果一个编辑器不能代码补全而另一个免费编辑器可以, 那么这可能就意味着他们永远不会尝试 Emacs.

    像 Spacemacs 和 Doom Emacs 这样的一体化软件包–Emacs 的两个" 大杂烩" 套件–的吸引力证明了第一印象的重要性和合理默认设置的价值.

    许多人直接使用这两个套件之一开始使用 Emacs, 同样多的人在新鲜感消退后转向了自己的 Emacs 配置: 但到那时, 他们已经终身成为 Emacs 用户了.

    这本书详细介绍了如何学习 Emacs, 你也一定会学会: Emacs 的术语早于现代计算, 但很容易学习, 因为只有少数几个术语将其与其他地方使用的 UI 术语区分开来. 按键绑定和命令更难学习, 但这让我们触及了 Emacs 的核心.

    Emacs 是一款复杂的软件, 你需要时间和努力去学习. 但是, 如果你做到了, 你将拥有一个终身受用的编辑器. 它拥有一个活跃, 友好的社区, 会吸收其他编辑器取得的任何进展并将其融入其中.

    而且–正如你将在本书其余部分看到的–这还不包括其他编辑器甚至无法比拟的无数好处.

    Emacs 的维护者深知这一点, 并在幕后不懈地致力于 Emacs 核心的开发, 旨在逐步改进 Emacs, 造福所有人, 同时保持向后兼容性.

    他们是优秀的管理者, 仔细权衡尊重 GNU 项目的哲学5, Emacs 的传统以及对稳定性的承诺的需求, 同时兼顾技术进步和用户反馈.

    你对 Emacs 的耐心掌握会得到丰厚的回报. 我向你保证.

    6

    https://www.gnu.org/software/emacs/manual/html_mono/efaq.html#Origin-of-the-term-Emacs

    本书只关注 GNU Emacs. 曾几何时, XEmacs 是功能更先进的编辑器, 但情况已不再如此: 从 Emacs 22 开始, GNU Emacs 就是市面上最好的 Emacs.

    XEmacs 和 GNU Emacs 的历史很有趣. 它是自由软件项目中最早的主要分支之一7.如今, XEmacs 已不再维护.

    7

    https://www.jwz.org/doc/lemacs.html

    对几乎所有人来说, Emacs 这个词特指 GNU Emacs. 我只在区分不同实现时才会写出全名. 当我提到 Emacs 时, 我总是指 GNU Emacs.

    由于 Emacs 的年代久远, 它存在一些……古怪之处. 奇怪的术语选择和历史遗留问题之所以持续存在, 是因为在大多数情况下, Emacs 在编辑器-IDE 发展曲线上领先了几十年, 因此不得不为事物发明自己的术语.

    有人提议用大家熟悉的词语替换 Emacs 自己的行话, 但如果真的发生, 那也将是很久以后的事了.

    尽管缺乏市场推广, 核心 Emacs 开发者人数不多, 以及那些早于现代个人计算时代的过时术语和用语, 仍有许多人就是喜欢使用 Emacs.

    Emacs 的力量在于其适应能力: 我指的不仅是软件本身, 还有那些设定 Emacs 方向的大量志愿维护者和贡献者. 他们孜孜不倦地工作–在受限于一个比大多数用户都年长的产品的情况下 –以跟上外部世界的变化. 所谓 Emacs 社区和平台僵化的说法–仅仅是个谣言.

    本章将讨论 Emacs 之道: 术语, Emacs 对许多人的意义, 以及为什么理解 Emacs 的起源会让你更容易接受它.

    8

    https://www.gnu.org/software/emacs/manual/eintr.html 在一个实用的环境中. 不要让括号吓到你; 它们实际上是它最大的优势.

    10

    透明远程 (文件) 访问, 多协议

    • 以及更多: 几乎每个编程环境的官方或非官方支持; 内置的 man page 和 info 阅读器; 一个非常复杂的目录和文件管理器; 对几乎所有主流版本控制系统的无缝支持; 以及成千上万个其他大大小小的功能.
    11

    https://en.wikipedia.org/wiki/IBM_Common_User_Access

    在" 选区兼容模式" 中, 我将解释如何切换到现代剪贴板按键, 但有一些注意事项, 以及为什么你不应该这样做. 相反, 我会告诉你为什么 Emacs 的系统更适合文本编辑.

    17

    站点范围的文件是一个全局设置文件, 类似于你自己的初始化文件

    如果 Emacs 在启动时向你显示错误消息, 你可以使用 -q 来阻止加载你的 init file. 如果这样解决了错误–那么你的 init file 就有问题了, 你应该采取措施来修复它: 恢复到旧版本, 注释掉代码直到它正常工作, 或者寻求帮助.

    Emacs 二进制文件遵循通常的命令行约定: emacs [switches] [file1, file2, …].

    Emacs 的方式是让它一直运行, 并在一个专用的 Emacs 实例中进行所有编辑. Emacs 的启动速度通常比其他编辑器慢 (因为它有更多的包和功能), 因为它是为长时间运行的会话而不是快速编辑而设计的.

    18

    https://xkcd.com/378/

    尽管如此, 组合键是日常 Emacs 使用的重要组成部分, 因此能够" 解码" 一串按键很重要.

    在 Emacs 中, 你可以使用几种组合键, 每种都有自己的字符表示:

    Modifier Full Name
    C- Control
    M- Meta (" Alt" 在大多数键盘上)
    S- Shift

    还有两个因历史原因存在的键 (Super 和 Hyper), 但在当今的键盘上没有专用按键, 但为了与 Space Cadet 键盘19 保持一致, 它们仍然在内部存在; 另一个键 (Alt) 在现代键盘上确实存在, 但在 Emacs 中被绑定 (并称为) Meta:

    Modifier Full Name
    s- Super (不是 shift!)
    H- Hyper
    A- Alt (冗余且未使用)
    19

    https://en.wikipedia.org/wiki/Space-cadet_keyboard

    Super 和 Hyper 仍然可以使用, 如果你拥有与 Microsoft Windows 兼容的 PC 键盘–或者使用可编程键盘–带有 Start 和 Application Context 按钮, 你可以将它们重新绑定以用作 Super 和 Hyper, 这是一种优雅地增加可用键空间的方法. Emacs 原生支持这些修饰键, 但你需要告诉你的操作系统或窗口管理器来绑定它们.

    重要 由于终端的限制, 如果你在终端中运行 Emacs, 有些按键绑定你根本无法输入. 我的建议是尽可能在 GUI 中运行 Emacs.

    不过, 了解修饰键只是问题的一半.

    在 Emacs 中, 我们正式定义一个按键序列 (key sequence) (或简称按键 (key)) 指的是一系列键盘 (或鼠标) 操作, 而一个完整按键 (complete key) 指的是一个或多个调用一个命令 (command) 的键盘序列; 如果按键序列不是一个完整按键, 那么你就有了一个前缀键 (prefix key). 如果 Emacs 完全无法识别该按键序列, 那么它是无效的, 并且会在回显区 (echo area) 显示一个错误.

    这个定义相当枯燥, 让我们来看几个例子.

    C-d 调用一个名为 delete-char 的命令. 要调用它, 按住 control 并按 d. 由于该按键是一个完整按键, 它将调用命令 delete-char 并立即删除光标后的字符.

    C-M-d 与上面的例子类似, 但这次你必须同时按住 control 和 meta, 然后再按 d.

    让我们尝试一些前缀键. 前缀键基本上是细分–一种对按键进行分组并增加可能按键组合数量的方法. 例如, 前缀键 C-x 有几十个与之绑定的按键. C-x 是一个你会一直使用的前缀键.

    C-x C-f 在 Emacs 中运行一个名为 find-file 的命令. 理解它的方法是先按住 control, 然后按下并释放 x. 在你的回显区 (echo area), Emacs 会在短暂的空闲 (大约一秒钟) 后显示 C-x- (末尾带有一个破折号), 这是 Emacs 告诉你它需要更多按键的方式. 最后, 输入 C-f, 这对你来说现在应该很容易做到: 按住 control 并按 f.

    要输入 C-x C-f, 你不必在每个按键之间释放 control 键–按住 control 键有助于你保持我称之为节奏 (tempo) 的东西, 我稍后会谈到.

    C-x 8 P 有两个前缀键: 首先是 C-x, 然后是 8, 这是 C-x 的一个子类别. 因此, 单独的 8 不会做任何事情 (它只会打印数字 8), C-x 或甚至 C-x 8 也不会–它们仍然是前缀键. 只有当你用 P 完成时, 该按键才算完整.

    我们将属于特定前缀键的一组按键称为按键映射 (key maps), 这是 Emacs 内部跟踪按键和命令之间映射的方式. 在本例中, 按键映射 C-x 8 包含各种用于书写或数学的实用字符, 但在大多数键盘上没有绑定. 例如, C-x 8 P 会插入段落符号 ¶.

    C-M-% 对初学者来说是一个棘手的按键. 使用你上面学到的知识, 按住 control 和 alt (正如你从上表中会记得的那样, Meta 就是 Alt), 还要按住 shift. % 字符通常与键盘数字范围上的一个数字共享, 这里的含义是你也必须按 shift.

    如果你不按 shift, 你实际上输入的是 C-M-5 (至少在美式键盘上是这样).

    值得一提的是, 这个特定的按键绑定到一个流行的命令 (M-x query-replace-regexp), 并且它是一个你无法在 Terminal Emacs 中输入的按键示例, 这是由于终端的技术限制 (而不是 Emacs 本身).

    TAB, F1–F12 等偶尔会这样写, 但也会用尖括号括起来: <tab>, <f1>. 重要的是不要将 TAB 与字符 T A B 混淆. 我只会使用前一种表示法以避免歧义.

    提示 如果你卡住了, 或者万一 Emacs 卡死了, 或者如果你输入了一个部分命令想要取消–按 C-g. 这是 Emacs 中通用的" 救我" 命令.

    20

    https://en.wikipedia.org/wiki/Space-cadet_keyboard

    在 Windows 上, 我推荐你使用 SharpKeys21. 在 Ubuntu 和 Mac OSX 上, 它是内置的; 进入键盘设置并更改它. 如果你使用其他 Linux 发行版, 你可能需要调整 xmodmap.

    21

    https://sharpkeys.codeplex.com/ (编者注: 该项目已归档, 可在 https://github.com/randyrants/sharpkeys 等处找到新版本)

    22

    如果你使用触摸打字, 这是一项首先值得学习的技能.

    23

    https://www.masteringemacs.org/article/evaluating-elisp-emacs

    • 重新启动 Emacs 是最简单的方法, 如果你在 Emacs 中弄坏了某些东西, 或者想确保一切在全新环境中正常工作, 我推荐这种方法.
    • M-x eval-buffer 会对你所在的整个缓冲区进行求值. 我用这个来求值某些东西.
    • M-x eval-region 只对你标记的区域进行求值.

    当然, 这只是冰山一角. 你不需要知道比这更多的东西来处理你看到并想尝试的零散代码片段.

    24

    https://www.emacswiki.org/emacs/ELPA

    键入 M-x package-list-packages, Emacs 应该会从你配置的仓库中检索包列表. 完成后, 会出现一个新缓冲区, 列出所有包. 与 Emacs 中的许多辅助缓冲区一样, 这个缓冲区也是超链接的. 随便浏览一下–你可以从包的详细信息页面一键安装你感兴趣的包.

    提示 如果你知道包的名称, 可以使用快捷键 M-x package-install 并在微缓冲区中输入名称. 和大多数微缓冲区提示一样, 这个也支持 TAB 补全. 包存档会不断更改, 尤其是当你让 Emacs 长时间运行时, 你的本地副本会变得陈旧; 要解决此问题, 你可以通过键入 M-x package-refresh-contents 来刷新目录.

    25

    正如我在" 按键" 一章中提到的, 你可以在前缀键后跟 C-h 来列出所有已知的绑定.

    我经常使用 describe system. 在写这本书的过程中, 我广泛使用了 info 手册和 apropos, 但 describe system 是我用来仔细检查我写的所有内容是否正确的工具. 如果你 कभी想知道 Emacs 中的某个符号 (无论是函数, 命令, 变量还是模式) 是做什么的, describe 会告诉你.

    doc string 唯一的小缺点是它假设了技术受众: info 手册通常不会这样. 这并非全是坏事, 你不必是 elisp 专家才能理解描述, 但你需要花一点时间来熟悉 doc string 中使用的术语.

    记住, describe system 描述的是一个活生生的系统–你个性化的 Emacs.

    你需要记住四个 describe 键, 因为它们是日常 Emacs 使用中最重要的.

    • M-x describe-mode 或 C-h m: 显示主模式 (以及任何已启用的辅模式) 的文档, 以及这些模式特有的任何按键绑定. describe 命令会查看你当前的缓冲区. 当你使用新的主模式时, 这个命令应该是你的首选. 你会通过这种方式发现 Emacs 的许多功能, 使用这个命令绝对至关重要.

    它不做的就是列出未绑定到任何键的特定于模式的命令: 它们根本不会显示.

    • M-x describe-command 或 C-h x: Emacs 28 中引入, 此命令仅描述命令, 而不像其下面的同级命令那样描述函数. 如果你正在寻找一个 interactive 命令, 我会使用这个.
    • M-x describe-function 或 C-h f: 描述一个函数. 这是掌握 Emacs 的关键路径上的另一个命令. 了解 Emacs 中的某些东西是做什么的 (以及如何查找它) 至关重要, 能够跳转到代码声明的部分也同样重要. 描述一个函数会给你 elisp 函数签名, 与之绑定的按键 (如果有), 一个指向其声明位置的超链接以及一个 doc string. 如果该函数是一个命令, 它会说它是 interactive 的. 如果你正在使用 Emacs 28 并且正在寻找命令, 我建议你使用 describe-command.
    • M-x describe-variable 或 C-h v: 描述一个变量. 与 describe-function 类似, 此命令也很重要, 但可能不那么重要, 因为对于初学者来说更改变量并不总是那么容易. 尽管如此, 能够阅读变量的作用是很有用的.
    • M-x describe-key 或 C-h k: 描述一个按键绑定的作用. 在所有命令中, 这是最值得记住的命令之一, 并且像 M-x describe-function 一样, 你会经常使用它. 如果你不确定某个按键绑定的作用, 只需进入 describe-key 界面并重新输入该按键–Emacs 就会告诉你它的作用. 值得记住的是, 某些按键来自主模式和辅模式, 并且不是全局的. 因此, 根据你输入命令的缓冲区, 你可能会得到不同的答案.

    Emacs 确实有更多 describe 命令, 但它们远不如日常使用实用. 既然你现在知道了 describe 命令的命名方式以及如何通过模式查找命令, 那么列出所有这些命令应该是一件轻而易举26的事情.

    26

    提示: apropos-command 是一个不错的起点.

    27

    在终端 Emacs 中需要.

    当你打开文件时, Emacs 会根据其扩展名 (如果失败, 则根据文件内容) 来猜测正确的主模式, 并且或多或少可以开箱即用. 除了上面的按键绑定外, 你可以像其他编辑器一样仅使用箭头键开始编辑和移动.

    让我们依次讨论每个命令, 因为它们简单的操作掩盖了其复杂性.

    30

    <精通 Emacs 中的按键绑定>(Mastering Keybindings in Emacs) 是关于这个主题的详细文章.

    将此添加到你的 init file:

    (global-set-key (kbd "M-[") 'tab-bar-history-back)
    (global-set-key (kbd "M-]") 'tab-bar-history-forward)
    

    现在, 当标签的窗口配置 (或其中一个窗口中的缓冲区) 发生更改时, 你可以在该历史记录中向前或向后移动.

    对于大多数工作流程, 窗口和标签栏管理足以满足你日常的 Emacs 窗口管理需求.

    31

    readline 的 man page 有一个完整的列表. 但为什么不在 Emacs 中阅读 man page 呢? M-x man RET readline RET

    32

    我将在编辑章节稍后讨论 kill 文本.

    然后 C-M-k for kill-sexp 会向上移动并 kill 你刚刚所在的平衡表达式: 之前:

    (+ (* █5 2) (- 10 10))
    

    用 C-M-u 上移一级后:

    (+ █(* 5 2) (- 10 10))
    

    用 C-M-k kill S-表达式后:

    (+ █ (- 10 10))
    

    我一直使用这个功能; 这是 Emacs 隐藏的瑰宝之一, 即使你不写 LISP, 也能让你非常高效. 例如, 像 Python 这样的语言到处都使用圆括号: 用于字典, 元组和列表. 将列表命令与 C-M-k 结合起来, 你可以轻松地重构大段代码, 并保持你的节奏, 因为大多数作用于平衡表达式的命令都绑定到 C-M- 修饰符.

    因为 C-M-d 会跳转到光标之后的下一个" 列表" 表达式–无论它在缓冲区中的什么位置–它也是一个强大的移动工具. 和 Emacs 中的所有东西一样, 意识到一个命令的潜力并将其牢记于心以便使用是困难的, 但回报是值得的.

    33

    过去使用 CUA 模式的一个原因是它的矩形模式功能. 该功能现在已内置, 不再需要 CUA 模式.

    34

    正如你可能记得的那样, kill 这个词在 Emacs 中是剪切的意思.

    35

    尽管这完全取决于你的颜色主题.

    在 Emacs 28 中, 你可以用 M-< 和 M-> 告诉 Emacs 跳转到缓冲区中的第一个或最后一个匹配项; 或者用 C-v 和 M-v 跳转到当前不可见的下一个或上一个匹配项. 不过, 这些按键绑定默认情况下未启用. 你必须自定义 isearch-allow-motion 来启用它们.

    跳转到缓冲区中的第一个或最后一个匹配项足够常见, 大多数人都能从中找到一些用处. 但我觉得跳转到缓冲区中不可见的匹配项的功能值得特别提及: 这是快速浏览包含大量匹配项的缓冲区的好方法. 每次调用都会跳转到该方向上第一个不可见的 Isearch 匹配项, 因此你一次可以获得一个窗口的匹配项.

    • 选择一个匹配项: 一旦你对某个匹配项满意, 你可以通过两种方式终止搜索:
      • C-g 退出 Isearch: 你的 isearch 会话已终止, 你将返回到原来的位置. 如果你的搜索字符串只有部分匹配, 它会首先将你返回到最后一个已知匹配项.
      • RET 选择匹配项: 这也会终止 Isearch. 但它会将你留在你所在的匹配项处, 并在你的原始位置留下一个标记, 这样你以后就可以用 C-u C-<SPC> 返回到原来的位置.

    Isearch 非常好用, 我强烈建议你用它来进行移动, 因为它是 Emacs 中最快的文本移动方式之一. 它也是我最常用的命令之一. 我每天使用它数百次, 如果不是更多的话. 将 Isearch 的行为牢记于心需要一些练习, 但它非常值得! 它有两个易于访问的按键–C-s 和 C-r–并且它是可视化的, 即时的. 没有干扰, 没有仪式, 没有弹出并遮挡你缓冲区的模态对话框, 没有繁琐的单选按钮来更改搜索方向, 不需要鼠标, 也不需要按 Tab键来操作它.

    37

    https://gnuwin32.sourceforge.net/packages/coreutils.htm

    你如何与 grep–或者更普遍地说, 与任何外部工具–交互取决于你的编辑器. 许多 Vim 用户会退出 Vim 或使用像 tmux 或 screen 这样的工具切换到终端并运行命令, 然后再返回 Vim. Emacs 用户更喜欢他们可以整合到 Emacs 中的工具. 从 Emacs 内部使用 grep 是一个主要的生产力提升器, 正如你很快就会看到的.

    38

    DWIM 代表 Do What I Mean (做我想做的)–另一种说法是 Emacs 会尝试猜测你想要做什么.

    你最可能用于原位 (in situ) 注释的两个键是 M-; 和 C-x C-;. 如果你键入 M-;, Emacs 会在光标所在行的末尾插入一个注释, 如果你在空行上, Emacs 会根据主模式的缩进规则缩进注释.

    如果你有一个活动区域, M-; 会在注释和取消注释之间切换. 命令 C-x C-; 会切换光标所在整行的注释. C-x C-; 也适用于负参数和数字参数.

    在注释中键入 M-j 或 C-M-j 会使 Emacs 断开该行并插入一个新注释. 从这个意义上说, 它与填充前缀 (fill prefix) 完全相同. 如果你编写大量文档字符串, 这个命令效果很好, 因为 Emacs 通常足够聪明, 能够识别某些文档字符串格式所需的注释前缀.

    值得一提的是, 我之前谈到的 Emacs 填充命令能够理解并尊重注释语法, 所以请随意在注释中使用 M-q.

    如果你在一个没有设置必要注释变量 (见下表) 的主模式中使用注释命令, Emacs 会在你第一次运行该命令时要求你输入要使用的注释字符.

    Variable Name Purpose
    comment-style 要使用的注释样式
    comment-styles 可用注释样式的关联列表
    comment-start 标记注释开始的字符
    comment-end 标记注释结束的字符
    comment-padding 注释字符和文本之间的填充 (通常是空格)

    以上所有变量都可以通过 M-x customize-option 进行自定义. 你不太可能需要更改 comment-start 或 comment-end, 因为它们几乎总是由主模式作者设置. 如果你的团队–或个人偏好–规定了一种注释样式而不是另一种, 那么 comment-style 是必须的. 要查看可用的注释样式列表, 你必须通过在 Customize 中阅读其描述或使用 M-x describe-variable (也绑定到 C-h v) 来查询变量 comment-styles.

    39

    PCRE 代表 Perl-Compatible Regular Expressions–一种由 Perl 编程语言发明的 regexp 样式.

    Emacs 的搜索和替换命令是:

    Key Binding Purpose
    C-M-% 查询正则表达式搜索和替换
    M-% 查询搜索和替换
    M-x replace-string 搜索和替换
    M-x replace-regexp 正则表达式搜索和替换

    你也可以从 Isearch 内部访问 Emacs 的搜索和替换功能:

    Isearch Key Binding Purpose
    C-M-% 查询正则表达式搜索和替换
    M-% 查询搜索和替换

    查询 (query) 命令是交互式的, 会在每个匹配项处提示你进行操作. 与 Isearch 类似, 界面相当简洁但实用. 它也分为两部分: 搜索和替换的提示, 其工作方式与其他提示相同, 以及你选择每个匹配项的交互部分.

    当出现匹配项时, 你可以选择以下选项之一:

    Query Key Binding Purpose
    SPC, y 替换一个匹配项, 然后继续
    . 替换一个匹配项, 然后退出
    , 替换, 但停留在当前匹配项
    RET, q 不替换直接退出
    ! 替换缓冲区中的所有匹配项
    ^ 将光标移回上一个匹配项
    u, U 撤销上一个/所有替换
    40

    下一个和上一个错误是一个糟糕的功能名称, 它适用于任何自动生成的列表, 例如 Occur, Grep, M-x compile 等.

    当宏录制正在进行时, 你会在模式行中看到 Def 这个词. 当你完成录制后, 你可以立即通过键入 C-x e 或 F4 来播放它.

    录制的宏有自己的宏环 (macro ring), 很像 kill ring, undo ring 和历史环 (history rings). 这意味着如果你开始一个新的宏, 不必担心意外覆盖已录制的宏. 它们永远不会真正丢失 (除非你退出 Emacs!), 但你可以显式地将它们保存到磁盘.

    你也可以将通用参数和数字参数传递给宏命令:

    Key Binding Description
    C-u F3 开始录制但追加到上一个宏
    C-u F4 播放环中的第二个宏
    numeric F3 开始录制但将计数器设置为数字
    numeric F4 将上一个宏播放 numeric 次

    所以, C-u 和数字参数做的事情不同. 在本例中, Numeric 指的是诸如 C-u 10 或 M-10 之类的数字.

    追加到上一个宏 (C-u F3) 有其用武之地, 但将数字参数传递给 F4 更值得了解, 因为按设定的次数重播宏是你更有可能做的事情. 通过传递数字 0 (例如 C-0 F4 或 C-u 0 F4), Emacs 会一遍又一遍地运行该宏, 直到 Emacs 发出错误信号, 例如到达缓冲区末尾, 或者当录制的宏发送错误信号时.

    41

    ASpell 可以在这里找到 https://aspell.net/win32/.

    我经常使用 M-$ 进行随手更正. 当你使用它时, Emacs 会告诉你它认为是否正确. 如果 Emacs 认为错误, 它会列出建议供你选择, Emacs 会替换原来的词.

    Flyspell mode 的工作方式与文字处理器中的内联拼写检查器完全相同–拼写错误的单词会用波浪线高亮显示. 然而, 该模式是为文本而非代码设计的; 对于代码, 请使用 M-x flyspell-prog-mode, 因为它将拼写检查限制在你的注释, 字符串和文档字符串中. 同样, 这是一个非常方便的功能.

    如果你启用任一 Flyspell 辅模式, 它还会启用一个绑定到 C-M-i (和 C-.) 的辅助命令, 该命令会自动更正光标处的单词. 它会选择第一个可能的匹配项并更正光标处的单词; 后续调用会在单词之间循环–比 M-$ 快得多, 因为 M-$ 会坚持询问你想要哪个更正.

    42

    正如你可能记得的那样, 你可以用 M-x customize-face 对其进行自定义.

    43

    如果找到 .dir-locals.el 文件, Emacs 会读取并求值一个 (可配置的) elisp 子集. 这是一个强大的概念, 但对于项目管理来说是一个糟糕的替代品.

    44

    如果你使用 IDO, 它默认不会在这里补全. 但 FIDO 会.

    与它们更通用的对应项不同, 像 C-x p f 和 C-x p b 这样的按键绑定工作方式相同, 但当然仅限于项目已知的文件. 所有命令都是如此: 它们将在项目定义的范围内操作–默认情况下, 这是 VC 根目录和任何已知文件.

    45

    当像 M-. 这样的命令将你从原来的位置带走时, 它们会留下标记, 以便你稍后可以返回.

    如果你用 M-. 调用 xref 的查找定义功能, 那么如果你的主模式尚未配置 xref, 你更有可能被要求首先查找 TAGS 表文件. TAGS 文件是一种生成标识符及其位置, 然后将其存储在文件系统上的大型静态查找表中的旧方法, 以提高性能. 如果你处理的是天文数字般庞大的项目, TAGS 文件效果很好. 但对于我们大多数人来说, 通常只需要在你的文件系统上动态搜索的工具 (或查询语言服务器).

    要开始使用, 我建议你尝试第三方包 dumb-jump, 尽管它的名字如此, 但它利用了像 grep, ack, ag 或 ripgrep 这样的文件搜索器的速度来查找与你的主模式相关的匹配项. 对于大多数情况, 它已经足够好了. 特别是 ripgrep 在现代硬件上快得令人难以置信, 并且与 Emacs 配合得很好46.

    46

    在包管理器中查看一下; 在包列表中键入 M-s o ripgrep 以查找提及它的包.

    配置完成后, 你可以用 M-. 跳转到标识符, 用 M-, 从堆栈中弹出标记并返回. 如果有多个定义, 或者 Emacs 不确定哪个是最好的, 你会看到一个包含匹配定义的缓冲区. 你也可以用 C-M-. 根据 regexp 进行匹配.

    47

    你需要哪些程序取决于平台, 但 M-x customize-group doc-view RET 会显示你需要的内容. 对于大多数 Linux 发行版, 它应该开箱即用.

    48

    透明远程 (文件) 访问, 多协议

    TRAMP 通过监视 C-x C-f (以及其他命令) 工作, 并在你尝试使用特殊语法 (与 scp 等命令行工具使用的语法并无不同) 访问远程文件时进行检测. TRAMP 之所以出色在于其完全的透明性. 如果你不知道 Emacs 具有远程编辑功能, 你永远不会知道. 它可以快速无缝地访问和编辑远程文件.

    所有 TRAMP 连接都遵循以下语法:

    /protocol:[user@]hostname[#port]:
    

    TRAMP 支持许多协议–新旧都有–但如今你最可能使用的是 ssh 或许还有 scp. 有关 TRAMP 协议的完整列表及其工作原理, 请查阅变量 tramp-methods 或 info 手册页 (tramp) Internal methods.

    49

    谢天谢地, 你可以使用 M-x proced 和 M-x man 作为两者的替代品.

    我个人几乎所有的命令行需求都使用 shell 模式. 我很少使用交互式终端程序, 当我需要时, 我可以使用 Emacs 的 M-x ansi-term 来进行适当的终端仿真.

    优点: 自由格式的文本编辑和移动, 因为 shell 模式只是一个简单的缓冲区, 这 outweigh 了缺点.

    Author: Kevin li

    Created: 2025-11-19 Wed 20:34