提到 Emacs,每个程序员应该或多或少听过其大名,毕竟 Emacs 已经有将近四十多年的悠久历史。不过由于 Emacs “入道”门槛较高,导致很多初学者还没领略其精髓就弃之而去。在“熟练”使用 Emacs 之前,我也被其虐的体无完肤,因此在使用 Emacs 四年后,觉得可以把个人经验分享出来,供有兴趣使用 Emacs 的读者参考。

本文将分两部分来介绍如何入门 Emacs,

  • 第一部分介绍核心理念,很多初学者在简单尝试 Emacs 之后就放弃的原因是没了解其设计理念。Emacs 是如此独特,以至于从其他编辑器转过来时有很多不适。

    通过了解其理念,读者可以判断 Emacs 是否符合自己的品味,是否值得花精力去掌握它

  • 第二部分会分享个人使用 Emacs 的过程,结合这个过程对初学者给出具体建议,之后会介绍日常使用的扩展。很多用户选择 Emacs 就是因为某个插件的某个功能,即使现在,我时常也会因为发现某些高级用法而感叹 Emacs 的威力。

虽然本文会用 ELisp 来举例,但读者并不需要学习 ELisp,具备基本编程技能即可。其次本文以现在较为流行的 VSCode 编辑器来对比 Emacs 的特点,换做其他编辑器也是成立的。

Emacs is Emacs. VSCode/Vim/Sublime… is yet just another editor. —– 出处

常用编辑器的学习曲线
常用编辑器的学习曲线

核心理念

GNU Emacs 官方网站是这么介绍的:

An extensible, customizable, free/libre text editor — and more.

前一部还算直接,“可扩展、可定制、自由的文本编辑器”,至于后面的 more 部分,则仁者见仁,智者见智。坊间一度传闻,Emacs 是伪装成编辑器的操作系统。这一节就来揭开 Emacs 的神秘面纱。

文本编辑

不论 Emacs 的追捧者再怎么神化它,首先 Emacs 是一个文本编辑器,然后才是其他。与 VSCode 不同的是,Emacs 是为纯键盘操作而设计的,与之类似的编辑器只有 Vi。纯键盘操作无疑比鼠标点击更高效。不过对于初学者来说,使用鼠标点击菜单栏中的功能可能会更方便些。

Emacs 图形界面
Emacs 图形界面

单就文本编辑而言,Emacs 就提供了很多贴心的功能,这里列举两处:

备份

作为一名成熟的程序员,每时每刻都需要有备份的意识,毕竟错误在所难免。在 Emacs 中主要提供两种备份方式,这里称为静态备份自动备份(auto saving)。

  • 静态备份发生在第一次打开文件时,备份的文件名结尾有 ~ 标示;而且支持多版本备份
  • 自动备份顾名思义,周期性保存当前正在编辑的文件,文件名头尾有 # 标示。在保存时,自动备份的文件会被删掉,而当因意外原因(如死机)导致 Emacs 崩溃时,文件则会保留,此时可通过 recover-this-file 命令来恢复。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
静态备份的文件
!Users!liujiacai!.emacs.d!README.org.~1~
!Users!liujiacai!.emacs.d!README.org.~2~
!Users!liujiacai!.emacs.d!customizations!editing.el.~1~*
!Users!liujiacai!.emacs.d!customizations!editing.el.~44~*
!Users!liujiacai!.emacs.d!customizations!editing.el.~45~*
!Users!liujiacai!.emacs.d!customizations!editing.el.~46~*
!Users!liujiacai!.emacs.d!customizations!editing.el.~47~*
!Users!liujiacai!.emacs.d!customizations!editing.el.~48~*

自动备份的文件
#!Users!liujiacai!.emacs.d!customizations!misc.org#
#!Users!liujiacai!.emacs.d!customizations!navigation.el#*
#!Users!liujiacai!.emacs.d!elpa!lsp-java-20201105.1758!lsp-java.el#
#!Users!liujiacai!.emacs.d!init.el#

上面是我电脑中部分的备份文件,得益于这两个功能,好多次把我从崩溃的边缘救回来。

Undo/Redo

在编辑文件时,撤销(Undo)和恢复(Redo)是很重要的功能,传统编辑器这两个操作都是线性的,而在 Emacs 中,则是树状的,这里通过一组文本编辑时的状态图来说明这两者的区别。

  • 依次输入 a b c 三个字符,进入到下图中的 current 状态
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;;                                o  (initial buffer state)
;;                                |
;;                                |
;;                                o  (first edit)
;;                                |
;;                                |
;;                                o  (second edit)
;;                                |
;;                                |
;;                                x  (current buffer state)
  • 此时,撤销两步,回到 first edit 状态(即只输入了字符 a),传统编辑器的此时的状态为
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;;                                o  (initial buffer state)
;;                                |
;;                                |
;;                                x  (current buffer state)
;;                                |
;;                                |
;;                                o
;;                                |
;;                                |
;;                                o

而 Emacs 则不然,其状态为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
;;                                o  (initial buffer state)
;;                                |
;;                                |
;;                                o  (first edit)
;;                                |
;;                                |
;;                                o  (second edit)
;;                                |
;;                                |
;;                                x  (buffer state before undo)
;;                                |
;;                                |
;;                                o  (first undo)
;;                                |
;;                                |
;;                                x  (second undo)

其状态是追加的,一次撤销意味着返回上次的状态,所以下面的状态图可能更合适些:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;;        (initial buffer state)  o
;;                                |
;;                                |
;;                  (first edit)  o  x  (second undo)
;;                                |  |
;;                                |  |
;;                 (second edit)  o  o  (first undo)
;;                                | /
;;                                |/
;;                                o  (buffer state before undo)
  • 此时,如果进行一次新的插入(比如字符 d),此时文本上的字符虽然都为 a d,但编辑器的状态图是不同的,如下所示:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
;;            Undo/Redo:                      Emacs' undo
;;
;;               o                                o
;;               |                                |
;;               |                                |
;;               o                                o  o
;;               .\                               |  |\
;;               . \                              |  | \
;;               .  x  (new edit)                 o  o  |
;;   (discarded  .                                | /   |
;;     branch)   .                                |/    |
;;               .                                o     |
;;                                                      |
;;                                                      |
;;                                                      x  (new edit)
  • 此时,如果再进行两次撤销,传统编辑器返回到 initial 状态(无任何字符),而 Emacs 则回到 second 状态(有 a b两个字符)。

初次接触 Emacs 中的撤销与恢复,十分容易让人迷惑,我时常也被这个搞得晕头转向,不过幸好有 Undo Tree 这个插件来可视化撤销状态,上面的状态图就取自 UndoTree 代码中的注释,感谢其作者的贡献。

扩展、定制

At its core is an interpreter for Emacs Lisp, a dialect of the Lisp programming language with extensions to support text editing.

上面一小节介绍了文本编辑中两个非常实用的基本功能,其实这只是冰山一角,Emacs 可扩展、可定制的特性营造了一个富有创造力的社区,拥有无数功能强大的插件。也许读者会疑问,VSCode 也有丰富的插件市场,那 Emacs 与它们有什么不同呢?这与 Emacs 设计架构有关。

Emacs 本身可以看作是一个虚拟机(Lisp Machine),核心是一个用 C 实现的 ELisp 解释器(interpreter)。除了与操作系统交互的部分使用 C 语言实现外,其余部分均由 ELisp 实现,由核心的 ELisp 解释器解析执行。

在 Emacs 中的所有操作,相当于在解释器中进行函数调用。比如字符输入会调用 self-insert-command 函数。这意味着用户自己定义的代码与 Emacs 的源码(ELisp 部分)是平等的,比如 Emacs 源码里面定义了 foobar 这么一个变量,那么用户自己编写的函数可以直接修改它。

如果读者对 Lisp 不熟悉,可以这么类比,打开 Emacs 的界面对应着在终端输入 python 进入的交互式 REPL,键盘输入、鼠标点击均对应一个个函数调用。

基于核心的 ELisp 解释器,对 Emacs 的扩展变得十分简单(熟悉 ELisp 的前提下),比如在 init.el 中定义下面几行代码即可在 Emacs 中打开浏览器,对指定关键词进行 Google 搜索。

1
2
3
4
5
6
7
8
9
(defun my/google-search ()
  "Googles a query or region if any."
  (interactive)
  (browse-url
   (concat
    "http://www.google.com/search?ie=utf-8&oe=utf-8&q="
    (if mark-active
        (buffer-substring (region-beginning) (region-end))
      (read-string "Google: ")))))

而 VSCode,即使是个 Hello World 级别的扩展,步骤也复杂的多,可参考:

基于 Emacs 功能完备的 ELisp 解释器,社区开发出了各式各样的插件,让一切都尽可能在 Emacs 中完成。比如听音乐玩游戏看 EPUB 电子书聊 Telegram,甚至任何应用都能运行在 Emacs 里!

Emacs, “a great operating system, lacking only a decent editor”

在 Emacs 听网易云音乐
在 Emacs 听网易云音乐
在 Emacs 玩俄罗斯方块
在 Emacs 玩俄罗斯方块
在 Emacs 阅读 EPUB 电子书
在 Emacs 阅读 EPUB 电子书
在 Emacs 进行 Telegram 聊天
在 Emacs 进行 Telegram 聊天
在 Emacs 使用 EAF 运行 aria2
在 Emacs 使用 EAF 运行 aria2

自由

提到 Emacs,不得不提的人是 Richard Stallman,江湖人称“教主”。早期的 Emacs 有很多版本,而现在 GNU Emacs 基本上已经统一江湖了。

Richard Stallman
Richard Stallman

教主极力推崇自由软件,关于自由软件的定义,可以在 GNU 官方网站 上找到,这里不再赘述。读者需要明确的是,GNU 社区中的 free 代表但是自由,而非免费。

自由软件毫无疑问极大促进了软件行业的发展,它让程序员有机会了解所用软件的实现机制,而 Emacs 作为教主早期的作品之一,毫无疑问继承这种思想。每一个操作都可以追根溯源,喜欢这种自由感觉。

更多 Emacs Hackers 可参考:

入门指南

经验分享

我接触 Emacs 是因为学习 Clojure,作为一门 Lisp,Emacs 毫无疑问是最佳的编辑器。不过由于原生 Emacs 功能“简陋”,多次尝试不得要领,直到遇到 braveclojure 上 Emacs 教程,把 emacs-for-clojure 的配置 clone 到本地,这才觉得 Emacs 也没那么“难用”。出于对 Lisp 的热爱,强迫自己尽量在 Emacs 中去编码,大概花了一两个月,度过了最艰难的适应期,以后就离不开它了。

到如今,我的配置文件已经丰富了许多,也有很多自己编写的函数。学习一门新语言之前,会把相关 Emacs 扩展先配置上,让一切都在 Emacs 中运行。这里我想着重强调一点:

论单个功能来说,Emacs 可能不是最好的,但如何有机的把各个功能组合起来,减少切换,Emacs 可能是最优的。

这里结合自己的踩坑、使用经验,给出下面几条建议:

  • 找个成熟的配置先把 Emacs 用起来,一开始不用去纠结细节,等有感觉后再去尝试自己定制。SpacemacsDoom Emacs 是社区内最流行的两个,建议初学者两个都去尝试下,找最合适自己
  • 找一个月重点突击,不要断断续续的使用 Emacs,否则很难适应它,一但过了这一个月,后面就是无限的“春风得意”
  • 在各种插件满足不了自己的需求、有 BUG 时(我大概是在两到三年后),学习 ELisp,毕竟这才是它的精髓。我自己是参考了 Learn X in Y minutes、Xah Lee 的 Practical Emacs Tutorial
  • 善用 C-h i,Emacs 自带的文档,以及 How do I find out how to do something in Emacs?
  • 接触社区,Emacs China 是我见过的质量最高的中文论坛,里面有很多资深的 Emacs Hacker 来解决初学者遇到的各种问题。
  • 截止到 2020/11 月初,我使用 Emacs 比较追求“原汁原味”,尽量用 Emacs 自己的快捷键(C-x C-s之类),虽然一年前小指就开始疼痛了,当时是把 CAPS 键映射为 Ctrl,这样用了一年左右,问题不能根治。虽然社区有推荐使用 evil(具有 Vim 的操作模式)来解决这个问题,但之前我觉得这不够“忠贞”,就一直没去用,直到最近发现了 viper mode 才意识到这种想法的幼稚,Emacs 的理念就是可以自己根据自身需求来定制,没有所谓的标准答案。对于其他编辑器的优点,Emacs 会吸收过来。于是立刻把 evil 配置上,从此彻底解放了我的小指。用了四年多的时间了,还是能从 Emacs 中学到些人生经验,估计这是其他软件做不到的。这点也促使我写这篇文章,防止初学者陷入这种思维定势。

当然每个人的学习道路都不一样,读者可根据自身情况来调整,尽读书不如不读书。一些中文学习资料:

插件推荐

Org-mode

在 org-mode 中编辑 UML
在 org-mode 中编辑 UML

Org-mode 这是很多非程序员选择 Emacs 的主要原因,简单来说它是一种类似 markdown 的标记语言,很多用户用它来记笔记、管理 GTD,借助于 Emacs 强大的扩展能力,程序员用它来进行文学编程(literate programming),当之无愧的排在插件榜的第一名。🥇

目前我对 org-mode 使用比较简单,只是把它当时 markdown 来用,单就这一点,配合上 Emacs 的快捷键,就已经甩各种 md 编辑器好几条街了,org-mode 对表格的支持也非常棒,比如可以使用 org-table-transpose-table-at-point 命令将表格的行列调换过来。

表格行列转换
表格行列转换

Magit

magit status
magit status
Magit 为 Emacs 用户提供操作 git 的接口,是我第一个重度依赖的 Emacs 插件,也是社区内排行第二的插件。Magit 深度整合到 Emacs 的快捷键中,一切 git 操作都如流水行云般,没有它的话,我连 rebase 都不会。

Evil

Evil Emacs steal my heart
Evil Emacs steal my heart
上面在个人经验里面以及提到过 evil,它并不是“邪恶”,而是 Extensible VI Layer for Emacs。Evil 除了把 vi 的 normal/insert/visual state(mode 在 Emacs 中另有所指) 移植过来外,还增加了 emacs state,用于禁用所有的 vi 功能。由于是在 Emacs 里面,我们完全可以自定义快捷键来覆盖 vi 的默认快捷键,达到 Emacs/Vi 的最佳融合,可以同时拥有 h j k lC-a, C-e, M-s, M-f, M-b。 拷贝 7 行的文本,在 evil 的 normal state 下,只需要

1
7 yy

而原生 Emacs 则需要

1
C-a C-SPC C-u 6 C-n C-e M-w

The best editor is neither Emacs nor Vim, it’s Emacs with Vim binding! —-出处

Dired

Dired 是 directory editor 的缩写,是 Emacs 内置的插件,类似 macOS 上的文件管理器 Finder。在 Dired 界面中,可以很方便的移动/删除/创建文件,更强大是可以直接批量修改文件,下图展示了如何批量把 test_foo_*.dat 重命名为 test_bar_*.dat,操作文件就像操作文本一样流畅。(图片来源

dired 批量重命名文件
dired 批量重命名文件

Ivy/Counsel/Swiper

Ivy/Counsel/Swiper 三件套是补全框架,它可以很方便的把当前操作的候选项以交互的方式展示出来,类似 VSCode 中的 Command Palette、Intellj 中的 Double Shift

虽然其他编辑器也有类似的功能,但是其功能不是局限,就是与其他功能分割,没有统一的体验。Emacs 则不同,再多的插件也能够有统一的体验,这一点是非常影响用户体验的。这里通过 ivy-occur + wgrep + evil 批量修改多个文件中的内容来说明 ivy 套件的强大功能。

演示的目录有 1.txt 2.txt 两个文件,内容都是 hello world,这里批量修改为 hello emacs。

ivy-occur 批量修改多个文件中的内容
ivy-occur 批量修改多个文件中的内容

操作步骤:

  1. counsel-ag world 搜索当前目录下搜索含 world 的文件
  2. C-c C-o ivy-occur 打开 occur 界面
  3. C-x C-q ivy-wgrep-change-to-wgrep-mode 进入编辑模式
  4. :%s/world/emacs/g 借助 evil 修改内容
  5. C-c C-c wgrep-finish-edit 保存内容

当然,上面操作可以根据自己的习惯定义快捷键,上述五个步骤一气呵成,“挥一挥衣袖,不带走一片云彩”。

Lsp-mode

lsp-mode
lsp-mode
LSP 出现之前,没有统一的框架来解决不同语言的高亮、补全等现代 IDE 的基本功能,微软推出的 LSP 无疑已经成为业界标准,无需使用正则这种既不准又耗自由的方式了。Emacs 中有两个扩展支持 LSP,分别是

  • Lsp-mode,主打开箱即用,默认提供传统 IDE 的所有体验
  • EGlot,主打小巧精致

目前我使用的是 lsp-mode,初学者可以都去尝试下,然后选择符合自己品味的那一款。

More

除了上面介绍的几个扩展外,日常使用的扩展还有如下几个,当然还有很多有趣的扩展等着读者自己去发现。

  • company 补全框架,可配合 lsp-mode 使用

  • multiple-cursors 多光标点操作

  • ace-jump-mode 根据字符,快速移动光标。下图为根据 p 进行快速跳转的示例

  • yasnippet 模板系统,通过定义代码片段的缩写来简化输入

  • flycheck 语法实时检查

  • treemacs 文件目录树导航

  • projectile 项目工作空间管理 上面图示展示了如何在一个项目中查找文件、在实现与测试中切换、切换不同的项目

总结

也许 Emacs 的普及程度远远比不上 VSCode,但这也并不算件坏事,比如那些伸手党们就不适合使用 Emacs,让他们进来只会拉低社区的整体水平;而且 Emacs 是个开放的系统,会借鉴 VSCode 中优秀的设计,Emacs 与其他编辑器并不是互斥的。 之前论坛就有台湾的 Emacs Twitter 账户维护者“叛逃”到 VSCode 的讨论,这类具有争议话题毫无疑问会引起大家的关注,但是别忘了 Emacs 自由的精神,符合自己的才是最好的,没必要一味沉迷在某个事物中,说到底,Emacs/VSCode 只是些工具而已,解决实际问题才是最重要的,当然一个舒心的操作系统编辑器,会让这个枯燥的过程变得乐趣无穷。 最后,借 Mastering Emacs 中的一句话结束本文

Your patient mastery of Emacs is well-rewarded. I assure you.

参考资料