走近 Emacs

浏览: 41 发布日期: 2016-08-17 分类: emacs

-1. 学习 Emacs 需要点好奇心

在 GNU/Linux、Windows、Mac OS X 这三大主流操作系统中,Emacs 都是可以用的,区别可能就是配置文件需要作一些轻微的调整。虽然运行 Android 系统的手机与平板也能安装相应的 Emacs 移植版本,但是无论如何我都要建议你不要去尝试在没有实体键盘的系统中使用 Emacs。

这个世界上,能跨这么多操作系统运行的程序并不太多。对于一款自由软件而言,能跨众多系统运行的唯一原因就是来自部分人类的需要。

对于『为什么要使用 Emacs?』这样的问题,我不想回答。因为提出这个问题的人差不多已经有了自己不打算使用 Emacs 的自洽的理由。

如果你是对自己活着的这个世界有点好奇心的人,那么当你听到有人说『Emacs 是个伪装成文本编辑器的操作系统』,去体验一下这个起源于计算机上古年代然而现在依然生机勃发的文本编辑器是怎样的一种存在,这并非浪费时间,而且 Emacs 的开发者与传教者们将自己可以变成李彦宏、马化腾之辈的智慧奉献给了 Emacs。他们的努力至少对得起你所付出的好奇与时间,而他们为何而努力这本身就是一件值得好奇的事。

0. 也许你需要的是 Emacs 官方使用说明书

请打开终端,执行以下命令:

$ emacs -q

在打开的 Emacs 窗口中,通过菜单『Help』->『Emacs Tutorial (choose language)』打开『Emacs 指南』的多语言版本选择界面,然后选择『Chinese-GB18030』即可打开『Emacs 指南』的中文版。这就是 Emacs 官方提供的『使用说明书』,为初学者提供了 Emacs 基本功能的概览。

Emacs 官方指南的优点是纲目并举,内容严谨,但是显然它比较适合采用广度优先遍历的方式阅读。我喜欢的阅读方式却是深度优先遍历,所以 Emacs 官方指南我一直都没有看完。

虽然 Emacs 的知识驳杂繁多,但弱水三千,仅需一瓢。因此这份文档主要围绕我所用的 Emacs 一些功能而逐步展开。

1. 将 Emacs 作为记事本使用

无论 Emacs 有多么强大的功能,然而它本质上是文本编辑器,而文本编辑器所做的事情无非就是打开一份文本文件,然后在编辑器的文本编辑区域输入文字。文本编辑任务结束后,再将文件保存至非易失性存储器。Windows 系统提供的记事本程序(notepad.exe)可完美的胜任这些任务,而 Emacs 可以完美的客串 notepad.exe 这个角色。

假设使用 Emacs 打开或新建一份名曰 demo.txt 的文本文件,只需:

$ emacs demo.txt

如果当前目录存在 demo.txt 文件,那么 Emacs 便会打开这份文件;反之,Emacs 会默默的在自己的世界里以 demo.txt 为名开辟一个 buffer(缓冲区)。

Emacs 的缓冲区是非常重要但也是非常简单的概念。缓冲区差不多是世界上所有文本编辑器的标准设施。我们在文本编辑器中输入的文本只是保存在计算机主存中的,只有调用文本编辑器提供的『文件保存』功能时,文本编辑器才会将缓冲区中的信息写入至非易失性存储器中对应的文件。这是文本编辑器最基本的功能,其副作用就是如果你不经常将缓冲区中的信息保存至文件,万一机器掉电,你辛苦输入的内容也就烟消云散了。

用 Emacs 打开或新建一份文本文件,本质上意味着用 Emacs 在计算机主存中开辟了缓冲区,所有编辑工作都在这块缓冲区中进行,除非使用 C-x C-s 命令进行文件保存。

所谓的 C-x C-s 命令是一套组合键。 C-x 表示:『摁住 Ctrl 键,再敲击(摁下去即刻松开) x 键,然后松开 Ctrl 键』。 C-s 与之类似。由于 C-x C-s 这套组合键需要摁下与松开 Ctrl 键两次,Emacs 认为这符合英语的连读语序,所以你完全可以摁住 Ctrl 键不放,然后敲击 x ,再敲击 s 键。

用一套组合键调用文本编辑器的『文件保存』功能,这等小事,本不必多言,更何况它比 notepad.exe 的快捷键复杂了一倍(后者只需 C-s ),但是如果我说『 C-x C-s 是一个名曰 save-buffer 的 Emacs Lisp 函数的键绑定』,这样可能会挽回一点 Emacs 的尊严。

也就是说,即使 Emacs 未提供 C-x C-s 这样的快捷键,依然可以使用 M-x 这个固定的快捷键去直接调用 save-buffer 函数,方法是:使用 M-x 组合键使得编辑器底部的微型缓冲区(Minibuffer)接受我们所输入的 Emacs Lisp 函数名及参数。由于 save-buffer 函数不需要参数,所以在微型缓冲区中输入 save-buffer 之后回车即可调用 save-buffer 函数,从而将 demo.txt 缓冲区中的信息写入文件。

所谓 M-x ,通常意味着摁住 Alt 键,然后敲击 x 键,然后松开 Alt 键。现在应该记住, C-x 是快捷键前缀,而 M-x 是调用 Emacs Lisp 函数的前缀。

2. Emacs Lisp 的三言两语

Emacs 的全部功能体现为一个数量庞大的 Emacs Lisp 函数集。 save-buffer 也许被用的最为频繁的 Emacs Lisp 函数之一。

所谓『Emacs Lisp 函数』,就是用一种名曰『Emacs Lisp』的 Lisp 方言写的函数。

所谓『Lisp 方言』,指的是一种像 Lisp 语言的语言。

所谓的『Lisp 语言』,则是被誉为『人工智能之父』的 John Maccarthy 于 1958 年为人工智能领域发明的一种编程语言。

Lisp 语言有许多方言,诸如 Common Lisp、Scheme、Emacs Lisp、Clojure、Lua、Ruby 等等。之所以会出现多种方言,与人类的各种语言也存在许多方言差不多。Lisp 语言出自 Maccarthy 之手,但是在不同的环境中经过五十多年的发展,出现方言毫不奇怪。

Emacs Lisp 脱胎于 MIT 人工智能实验室早期开发的一种 Lisp 方言——MacLisp,后来又受了 Common Lisp 的一些影响。事实上,Richard Stallman 在开发 Emacs Lisp 之时,Scheme 这个简洁优美的 Lisp 方言已经出现,但其解释器的运行效率不足以作为一款文本编辑器的扩展语言。

Stallman 创造 Emacs 的主要手法是用 C 语言编写与计算机硬件直接作用的模块,然后用 C 语言写出 Emacs Lisp 语言的解释器,最后用 Emacs Lisp 语言编写 Emacs 的文本编辑功能。许多年后,Paul Graham 在《黑客与画家》中提到了『格林斯潘第十定律』:『任何C或Fortran程序复杂到一定程度之后,都会包含一个临时开发的、只有一半功能的、不完全符合规格的、到处都是bug的、运行速度很慢的Common Lisp实现』。伟大的 Stallman 从一开始就让 Emacs 项目巧妙使得这个定律失效了。

Lisp 语言从计算机科技的上古时代跨越到了现代,它的存在也许是对这数十年来短视的人类的嘲弄。因为直到今天,最高级的主流语言,终于在功能上有些像 Lisp 了。无独有偶,Emacs 也参与了这种嘲弄——现代的最高级的文本编辑器,终于在功能上也有些像 Emacs 了!

3. 缓冲区、窗口与窗框

Emacs 为文本编辑提供的最基本的设施是缓冲区(Buffer)、窗口(Window)与窗框(Frame)。

文件,就是位于非易失性存储器(硬盘、U 盘等)上的一块区域。Emacs 可将文件中存储的信息读入缓冲区,也可以将缓冲区中的信息写入文件。窗口是用来编辑与显示缓冲区中信息的设施。窗框是包含窗口的设施。

Emacs 的窗口功能,我一直觉得是神来之笔。大部分文本编辑器,缓冲区与窗口是用铆钉固死的,而 Emacs 的缓冲区与窗口应该是螺栓连接。也就是说,在 Emacs 一个窗口中,可以切换显示不同的缓冲区。同理,一个缓冲区也可以在多个窗口中显示,因为窗框支持无限多的窗口。

Emacs 启动后,窗框中默认只有一个窗口,但是使用 C-x 1C-x 2 可以将其水平或竖直一分为二,这个分割过程可以无限的进行下去。每次被分割的窗口都是输入光标所在的窗口(也称为被激活的窗口)。使用 C-x o 可以在多个窗口中跳转。

如果是图形界面的 Emacs,可直接使用鼠标点击要激活的窗口来实现在多个窗口中的跳转,而且比 C-x o 更方便。即便如此,记住 C-x o 依然是必要的。因为你的鼠标有时会坏掉,有时你用的 Emacs 并非图形界面而是终端界面。如果你不懂我在说什么,可在终端中执行以下命令:

$ emacs -nw 你要编辑的文件

由于 Emacs 在拆分窗口时,默认是水平或竖直均分,所以如果你希望将某个窗口调的宽一些,可 C-x } ;要使之窄一些,可 C-x { ;要使之高一些,可 C-x ^,但是要使之矮一些,貌似只能是激活该窗口下方的窗口,然后让下方的窗口高一些。这些窗口大小的调整方法,每次只能使得窗口的尺寸增大或减小一个字符的宽度或高度。如果你不想多次输入像 C-x ^ 这样的命令,那么可以用 C-u n C-x ^ 这样的组合键,其中 n 表示你要重复执行 C-x ^ 这样的命令 n 次。例如,要将当前窗口的高度增加 5 个字符的高度,只需 C-u 5 C-x ^ 即可。

C-u 最好要牢记,它的职能就是给某个组合键所绑定的 Emacs Lisp 函数传递一个参数值。 C-u 5 C-x ^ 表示向 C-x ^ 所绑定的 Emacs Lisp 函数传递参数值 5 ,而这个函数接受这个参数值之后,就会将窗口的高度增加 5 个字符的高度。

要查看按键被绑定到了哪个 Emacs Lisp 函数,只需 C-h k <RET> 按下你要查询的键 。 <RET> 表示回车键。

如果觉得窗口太多,想关掉一些,那么关闭被激活的窗口的组合键是 C-x 0 。如果是图形界面的 Emacs,只需要鼠标右键点击窗口的模式行即可将该窗口关闭。我的建议是:能用键盘就不要用鼠标

要在某个窗口中打开或新建一个文件,可 C-x C-f 文件路径

为了节省内存占用,请尽量使用 Emacs 的多窗口模式,不要打开一个又一个 Emacs。

4. 文本编辑的正确姿势

在 Emacs 窗口中输入文字,这也许是 Emacs 世界中最没有难度的一件事,只要你的输入法能在 Emacs 窗口中输入文字。但是这件事兴许有可能演变为世界无人能解决的『难题』——突然有一天,你用的中文输入法在图形界面的 Emacs 中不工作了!

当中文输入法在图形界面的 Emacs 中不工作时,不要紧张也不要沮丧,因为 Emacs 的终端界面总是差不多总是可用的。这也是前文之所以对鼠标操作 Emacs 的行为提出告诫的主要原因。

复习:在终端中,执行 emacs -nw 或 emacs --no-window-system 命令即可开启 Emacs 的终端界面。

如果你不担心中文输入法问题,那么接下来应该关注的想必是如何操纵窗口中的既有文本。例如,在某行文本中发现了输入错误,需要删除错误的文字,然后补正。对于这个任务,如果你用的是图形界面的 Emacs,想必你又会情不自禁的动用鼠标将光标移动到出错的位置,然后用 Backspace 键去删除错误的文字。重要的事,往往需要提醒三遍,假如你只有终端界面的 Emacs 可用,鼠标是救不了你的。

用 Emacs 的正确姿势应该是永远保持能不用鼠标就不用鼠标的姿势!因此,你需要学会用键盘来控制光标在窗口中的位置。

最基本的光标位置控制键如下所示:

               上一行 C-p
                    :
                    :
向左移 C-b .... 目前光标位置 .... 向右移 C-f
                    :
                    :
               下一行 C-n

要记忆这几个键并不困难。 p 就是 previousn 就是 nextb 就是 backwardf 就是 forward,而 C 就是 Control……美国人民真会玩!

上述按键虽然简单易懂,但它们只适合小范围移动光标。在行内,可以用 M-fM-b 前后大步移动光标,步进单位对于英文而言是单词的长度,对于中文而言是两个标点符号的间距。

对于英文文本,若将光标快速移到行首或行尾,标准按键是 C-aC-e 但是对于中文而言,这对按键有着将光标被移到段首或段尾的奇异效果……如果偶尔忘记了这对按键,用 HomeEnd 键也未尝不可。 M-aM-e 则分别可将光标移动到句首或句尾。

如果文档很长,用 C-vM-v 可实现向下或向上翻屏。如果偶尔忘记了这对按键,用键盘上的 PgUpPgDn 翻页键也可行。

跨度最大的光标移动键是 M-<M-> ,可分别将光标移动到缓冲区的首部与尾部。

一旦掌握了上述这几个控制光标位置的按键,在 Emacs 中鼠标基本上就变得不是那么不可或缺了,更重要的是,这些按键能够帮助你更快速的选择文本。对于文档中的待选文本区域,用上述按键将光标移动到该区域的起始位置,然后用 C-@ 标定选区之首,然后继续用上述按键将光标移动到选区之尾,这样就完成了文本的选取。

你可能不知道怎么产生 C-@ 按键序列。普通键盘上,应该是摁住 Ctrl 键,然后再摁住 Shift 键,最后摁下数字 2 键,然后松开手。因为 @2 上面,需要 Shift 切换……

对于选中的文本,随后的操作无非是剪切、复制或删除。剪切,请用 C-w;复制,请用 M-w;删除,请用 Backspace。对于剪切或复制的文本,要粘帖在光标所在位置,请用 C-y……或者你也可以试着看看 C-y 之后再 M-y 有什么效果!

对文本进行了一段时间的编辑之后,要反悔的话,就用 C-_ 键执行 Undo 操作。

要产生 C-_ 的按键序列,请参考上文所讲的 C-@ 的做法。

建议现在就立刻将上述提到的每个按键都反复试验六、七次,以后就一直坚持使用它们……用不了几天也许就变成手指的本能了。忘掉鼠标吧,键盘可以解放你的双手,可以让你从畸形的鼠标手解脱,然后变成键盘手……因为你很快就会觉得左手的小指会有点酸痛!我会在下一节中拯救你的手。

5. 拯救你的左手小拇指!

如果感到左手的小手指有点疼,就不要再去用左手去摁 C-x C-s 这样的键了,你完全可以用右手的小指去摁键盘右侧的 Ctrl 键,然后左手相应的手指去摁 x 与 s 键!

如果你幸好懂得一些数字电路技术,也可以做一对 USB 接口的脚踏板,然后用左脚踏板模拟 Ctrl 键,右脚踏板模拟 Alt 键(Emacs 称之为 Meta 键)。

很可惜,我上面的谆谆教导可能对很多人都无效,因为他们往往是用一指禅或二指禅打字……那么就自行揣摩如何让自己的双手不要患上 Emacs 病!

6. 将一段代码向右推

虽然不如项羽力拔山兮气盖世,但是 Emacs 能够不费吹灰之力推动一段代码使之向右平移。

例如下面的代码:

#include <stdio.h>

int main(void)
{
        printf("Hello World!");

        return 0;
}

现在要解决的问题是将这段代码整体向右平推三个空格的距离,也就是在这段代码每一行的行首插入三个空格。

Emacs 提供了矩形区域选取文本的功能,在选区的起始位置使用 C-x <SPC> 进入矩形选择模式,然后运用前文所述的移动光标的几个按键,即可构造出如下图所示的矩形高亮选区。

对于矩形选取内的文本,有一种方法可将其中的每一行替换为指定的字符串,即 C-x r t 指定的字符串 <RET>。对于上面图中所示的矩形选取,若将其每行替换为 20 个空格字符,只需 C-x r t C-u 20 <SPC> <RET>,这样就可以将整个矩形选区抹为空白了。如下图所示:

现在解读一下刚才那句咒语。 C-x r 驱使 Emacs 进入矩形域编辑模式, t 表示要对矩形选区内的每行文本进行替换。 C-u 20 <SPC> 可以构造 20 个空格组成的字串, <RET> 之后,大功告成。

现在,再回到本节开始所提出的那个问题上……我觉得解决方法已经显而易见了。于是,你就掌握了一种非常有效的在 reStructuredText 标记文档中排版代码的技巧。

有关 Emacs 的矩形区域编辑功能更多的知识,建议阅读 Emacs 手册的 Rectangles 一节。在 Emacs 中打开 Emacs 手册的方法是 C-h r。快速找到 Rectangles 这一节的方法是:打开 Emacs 手册后,用 <CapsLock> T 键打开目录,然后用 Ctrl-s Rectangles 进行搜索。找到 Rectangles 节之后,将光标移到像超级链接一样的文本上,然后 <RET> 即进入该节。

7. 文本的查找与替换

C-sC-r.

C-M-sC-M-r 。注意,fcitx 输入法可能会将 C-M-s 按键拦截。

M-x replace-stringM-x replace-regexp

M-x query-replaceM-x query-replace-regexp

上述按键的用法作为本文的课后习题,自行去了解……如果需要查看 Emacs 对它们的描述,例如查看 C-s 的信息,可如此这般 C-h k <RET> C-s,只要别傻呵呵的真的以文本的形式输入了 C-s

8. 拿出 15 分钟了解一下 Emacs Lisp 如何?

下面是国外黑客写的一份简短的 Emacs Lisp 入门指南,只要跟着他说的做下去,再考虑语言的差异以及自身的懒惰情绪,大概半小时左右能够近距离的体验一下 Emacs Lisp。试试看!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Fire up Emacs.
;;
;; Hit the `q' key to dismiss the welcome message.
;;
;; Now look at the gray line at the bottom of the window:
;;
;; "*scratch*" is the name of the editing space you are now in.
;; This editing space is called a "buffer".
;;
;; The scratch buffer is the default buffer when opening Emacs.
;; You are never editing files: you are editing buffers that you
;; can save to a file.
;;
;; "Lisp interaction" refers to a set of commands available here.
;;
;; Emacs has a built-in set of commands available in every buffer,
;; and several subsets of commands available when you activate a
;; specific mode.  Here we use the `lisp-interaction-mode', which
;; comes with commands to evaluate and navigate within Elisp code.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Semi-colons start comments anywhere on a line.
;;
;; Elisp programs are made of symbolic expressions ("sexps"):
(+ 2 2)

;; This symbolic expression reads as "Add 2 to 2".

;; Sexps are enclosed into parentheses, possibly nested:
(+ 2 (+ 1 1))

;; A symbolic expression contains atoms or other symbolic
;; expressions.  In the above examples, 1 and 2 are atoms,
;; (+ 2 (+ 1 1)) and (+ 1 1) are symbolic expressions.

;; From `lisp-interaction-mode' you can evaluate sexps.
;; Put the cursor right after the closing parenthesis then
;; hold down the control and hit the j keys ("C-j" for short).

(+ 3 (+ 1 2))
;;           ^ cursor here
;; `C-j' => 6

;; `C-j' inserts the result of the evaluation in the buffer.

;; `C-xC-e' displays the same result in Emacs bottom line,
;; called the "minibuffer".  We will generally use `C-xC-e',
;; as we don't want to clutter the buffer with useless text.

;; `setq' stores a value into a variable:
(setq my-name "Bastien")
;; `C-xC-e' => "Bastien" (displayed in the mini-buffer)

;; `insert' will insert "Hello!" where the cursor is:
(insert "Hello!")
;; `C-xC-e' => "Hello!"

;; We used `insert' with only one argument "Hello!", but
;; we can pass more arguments -- here we use two:

(insert "Hello" " world!")
;; `C-xC-e' => "Hello world!"

;; You can use variables instead of strings:
(insert "Hello, I am " my-name)
;; `C-xC-e' => "Hello, I am Bastien"

;; You can combine sexps into functions:
(defun hello () (insert "Hello, I am " my-name))
;; `C-xC-e' => hello

;; You can evaluate functions:
(hello)
;; `C-xC-e' => Hello, I am Bastien

;; The empty parentheses in the function's definition means that
;; it does not accept arguments.  But always using `my-name' is
;; boring, let's tell the function to accept one argument (here
;; the argument is called "name"):

(defun hello (name) (insert "Hello " name))
;; `C-xC-e' => hello

;; Now let's call the function with the string "you" as the value
;; for its unique argument:
(hello "you")
;; `C-xC-e' => "Hello you"

;; Yeah!

;; Take a breath.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Now switch to a new buffer named "*test*" in another window:

(switch-to-buffer-other-window "*test*")
;; `C-xC-e'
;; => [screen has two windows and cursor is in the *test* buffer]

;; Mouse over the top window and left-click to go back.  Or you can
;; use `C-xo' (i.e. hold down control-x and hit o) to go to the other
;; window interactively.

;; You can combine several sexps with `progn':
(progn
  (switch-to-buffer-other-window "*test*")
  (hello "you"))
;; `C-xC-e'
;; => [The screen has two windows and cursor is in the *test* buffer]

;; Now if you don't mind, I'll stop asking you to hit `C-xC-e': do it
;; for every sexp that follows.

;; Always go back to the *scratch* buffer with the mouse or `C-xo'.

;; It's often useful to erase the buffer:
(progn
  (switch-to-buffer-other-window "*test*")
  (erase-buffer)
  (hello "there"))

;; Or to go back to the other window:
(progn
  (switch-to-buffer-other-window "*test*")
  (erase-buffer)
  (hello "you")
  (other-window 1))

;; You can bind a value to a local variable with `let':
(let ((local-name "you"))
  (switch-to-buffer-other-window "*test*")
  (erase-buffer)
  (hello local-name)
  (other-window 1))

;; No need to use `progn' in that case, since `let' also combines
;; several sexps.

;; Let's format a string:
(format "Hello %s!\n" "visitor")

;; %s is a place-holder for a string, replaced by "visitor".
;; \n is the newline character.

;; Let's refine our function by using format:
(defun hello (name)
  (insert (format "Hello %s!\n" name)))

(hello "you")

;; Let's create another function which uses `let':
(defun greeting (name)
  (let ((your-name "Bastien"))
    (insert (format "Hello %s!\n\nI am %s."
                    name       ; the argument of the function
                    your-name  ; the let-bound variable "Bastien"
                    ))))

;; And evaluate it:
(greeting "you")

;; Some function are interactive:
(read-from-minibuffer "Enter your name: ")

;; Evaluating this function returns what you entered at the prompt.

;; Let's make our `greeting' function prompt for your name:
(defun greeting (from-name)
  (let ((your-name (read-from-minibuffer "Enter your name: ")))
    (insert (format "Hello!\n\nI am %s and you are %s."
                    from-name ; the argument of the function
                    your-name ; the let-bound var, entered at prompt
                    ))))

(greeting "Bastien")

;; Let's complete it by displaying the results in the other window:
(defun greeting (from-name)
  (let ((your-name (read-from-minibuffer "Enter your name: ")))
    (switch-to-buffer-other-window "*test*")
    (erase-buffer)
    (insert (format "Hello %s!\n\nI am %s." your-name from-name))
    (other-window 1)))

;; Now test it:
(greeting "Bastien")

;; Take a breath.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Let's store a list of names:
(setq list-of-names '("Sarah" "Chloe" "Mathilde"))

;; Get the first element of this list with `car':
(car list-of-names)

;; Get a list of all but the first element with `cdr':
(cdr list-of-names)

;; Add an element to the beginning of a list with `push':
(push "Stephanie" list-of-names)

;; NOTE: `car' and `cdr' don't modify the list, but `push' does.
;; This is an important difference: some functions don't have any
;; side-effects (like `car') while others have (like `push').

;; Let's call `hello' for each element in `list-of-names':
(mapcar 'hello list-of-names)

;; Refine `greeting' to say hello to everyone in `list-of-names':
(defun greeting ()
    (switch-to-buffer-other-window "*test*")
    (erase-buffer)
    (mapcar 'hello list-of-names)
    (other-window 1))

(greeting)

;; Remember the `hello' function we defined above?  It takes one
;; argument, a name.  `mapcar' calls `hello', successively using each
;; element of `list-of-names' as the argument for `hello'.

;; Now let's arrange a bit what we have in the displayed buffer:

(defun replace-hello-by-bonjour ()
    (switch-to-buffer-other-window "*test*")
    (goto-char (point-min))
    (while (search-forward "Hello")
      (replace-match "Bonjour"))
    (other-window 1))

;; (goto-char (point-min)) goes to the beginning of the buffer.
;; (search-forward "Hello") searches for the string "Hello".
;; (while x y) evaluates the y sexp(s) while x returns something.
;; If x returns `nil' (nothing), we exit the while loop.

(replace-hello-by-bonjour)

;; You should see all occurrences of "Hello" in the *test* buffer
;; replaced by "Bonjour".

;; You should also get an error: "Search failed: Hello".
;;
;; To avoid this error, you need to tell `search-forward' whether it
;; should stop searching at some point in the buffer, and whether it
;; should silently fail when nothing is found:

;; (search-forward "Hello" nil t) does the trick:

;; The `nil' argument says: the search is not bound to a position.
;; The `t' argument says: silently fail when nothing is found.

;; We use this sexp in the function below, which doesn't throw an error:

(defun hello-to-bonjour ()
    (switch-to-buffer-other-window "*test*")
    (erase-buffer)
    ;; Say hello to names in `list-of-names'
    (mapcar 'hello list-of-names)
    (goto-char (point-min))
    ;; Replace "Hello" by "Bonjour"
    (while (search-forward "Hello" nil t)
      (replace-match "Bonjour"))
    (other-window 1))

(hello-to-bonjour)

;; Let's colorize the names:

(defun boldify-names ()
    (switch-to-buffer-other-window "*test*")
    (goto-char (point-min))
    (while (re-search-forward "Bonjour \\(.+\\)!" nil t)
      (add-text-properties (match-beginning 1)
                           (match-end 1)
                           (list 'face 'bold)))
    (other-window 1))

;; This functions introduces `re-search-forward': instead of
;; searching for the string "Bonjour", you search for a pattern,
;; using a "regular expression" (abbreviated in the prefix "re-").

;; The regular expression is "Bonjour \\(.+\\)!" and it reads:
;; the string "Bonjour ", and
;; a group of           | this is the \\( ... \\) construct
;;   any character      | this is the .
;;   possibly repeated  | this is the +
;; and the "!" string.

;; Ready?  Test it!

(boldify-names)

;; `add-text-properties' adds... text properties, like a face.

;; OK, we are done.  Happy hacking!

;; If you want to know more about a variable or a function:
;;
;; C-h v a-variable RET
;; C-h f a-function RET
;;
;; To read the Emacs Lisp manual with Emacs:
;;
;; C-h i m elisp RET
;;
;; To read an online introduction to Emacs Lisp:
;; https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html

;; Thanks to these people for their feedback and suggestions:
;; - Wes Hardaker
;; - notbob
;; - Kevin Montuori
;; - Arne Babenhauserheide
;; - Alan Schmitt
;; - LinXitoW
;; - Aaron Meurer

9. 回顾一下你的 init.el 文件

一旦对 Emacs Lisp 有所了解之后,可能就不再害怕 $HOME/.emacs.d/init.el 文件了。虽然文件里的很多东西你依然不了解,但是你可以很放心的把它们交给时间来保管。

返回顶部