前一篇《Emacs 是一台计算机》
理解了 Emacs 身为计算机的本质之后,在 Emacs 里编程就顺理成章了。不过,在此之前,还需要略微介绍一下 Emacs 最基本的操作。
系统的不一致,令人有点烦躁
现在,也可以坦然地说,Emacs 是一台虚拟的计算机,目前,它只能在宿主操作系统中运行,而这个宿主操作系统可以是 Windows、Linux 或 Mac OS X。由于这个世界上绝大多数计算机所运行的操作系统不外乎这三者之一,因此无需担心无 Emacs 可用。
现在假设你对自己所用的操作系统足够熟悉,至少达到能够从 https://www.gnu.org/software/... 页面找到与自己所用的操作系统匹配的 Emacs 版本的安装包并成功安装的程度,那么我就假设你的操作系统中已经存在了一个可用的 Emacs。此外,我还得假设你知道怎么打开你所用的操作系统的命令行窗口。
命令行窗口,在 Windows 里,叫控制台或命令行提示符,而在 Linux 与 Mac OS X 里,则称为终端(Terminal)。
实际上,Windows 里所谓的命令提示符,这真的是一个很奇葩的名字,它实际上是以命令行窗口中里 >
符号的名字。在 Linux 与 Mac OS X 的终端里,命令提示符是 $
。所谓命令提示符,意思是在这个符号的后面可以输入命令,这个符号本身不需要你输入。下文以 $
作为命令提示符。
启动 Emacs 的命令如下:
$ emacs
执行这条命令之后,可以得到如图 1 所示的窗口,这或许是你有生以来第一次看到 Emacs,记住这一天。
接下来,在 Emacs 里执行第一个命令 C-x C-f
。这是 Emacs 特色的组合按键序列的表现形式,其含义:摁住 Ctrl 键,单击 x
键;摁住 Ctrl 键,单击 f
键。执行这一命令后,观察 Emacs 窗口,它的底栏应该会出现 Find file: ...
字样,其中 ...
是 Emacs 的当前工作目录的路径。在此,我不得不继续假设你知道在一个操作系统中一个工作目录的路径指的是什么。或许 Windows 用户对此不甚了解,下面略作解释。
当你打开 Windows 命令提示符窗口的时候,窗口中应该会出现像下面这样的字样:
C:\Users\用户名 >
这个 C:\Users\用户名
就是当前的工作目录的路径。
再回到 C-x C-f
按键在 Emacs 底栏召唤出来的 Find file: ...
,现在,在 ...
之后输入 foo.txt
,然后击回车(Enter)键,这样便在工作目录中创建了一份名称为 foo.txt 的文本文件。这份文件没有什么特殊意义,它只不过是为了让你体验 Emacs 而随便创建的一份文本文件。
从现在开始,将 Emacs 用于接受我们输入 foo.txt
文件名的底栏称为微缓冲区(Minibuffer)。微缓冲区是我们向 Emacs 传递各种按键序列以及命令的通道。
当 Emacs 在工作目录中创建一份新的文件时,它会在微缓冲区上方的窗口中为这份文件创建一个缓冲区。这个缓冲区就叫缓冲区,它对应一块内存区域,可以与硬盘上的文件相关,也可以无关。以后,当我谈及「缓冲区」的时候,指的就是微缓冲区上方的缓冲区。
我们在缓冲区中编辑文本,在需要将所编辑的内容保存到与缓冲区相关联的文件里之时,只需向 Emacs 发出 C-x C-s
指令——摁住 Ctrl 键,击 x
键;摁住 Ctrl 键,击 s
键。
像 C-x C-f
与 C-x C-s
这样的组合键,由于每组按键都需要摁住 Ctrl 键,因此可以采用类似「英文连读」的方式减少按键次数,即摁住 Ctrl 键,依次单击 x
与 f
,或依次单击 x
与 s
。
我们对 Emacs 的操作暂时只需了解这么多。倘若你学有余力,可继续阅读我写的另一份文档 [1]。
hello, world
这个世界上,被重复写过最多的一个程序,它的名字叫 hello world。始作俑者是《The C Programming Language》的作者 Brain Kernighan。无数初学编程的人,用这个程序向计算机软件世界发出了第一声问候。
在 Emacs 中,我们不妨也贯彻一下这个仪式。在上一节所建立的 foo.txt 文件缓冲区里输入 hello, world
。
对此,请不要吝惜发出~噫~的声音!
若真的是这样的 hello world,不论是谁,都会感到失望。不过,由于我此前将 Emacs 的窗口(还记得俄罗斯方块吗)称为显示器。这种直接在缓冲区里键入 hello, world
的方式其实很黑客,这是直接修改 Emacs 的显存内容啊!
发出~噫~的声音的人说,散了,散了,他就这两下子!
当然不是。要通过一段程序,在 Emacs 窗口中显现 hello, world
,这需要了解一下 Emacs 的启动配置文件 init.el。要了解这个文件,需要继续为三大操作系统的不一致而烦躁。
Emacs 的 HOME 目录
init.el 文件是 Emacs 的启动配置文件。它应该位于 HOME 目录的 .emacs.d 子目录中。
对于 Windows 用户,倘若你的操作系统安装在 C 盘。对于 Windows 7 或更新的版本,Emacs 会将 C:\Users\<用户名>\AppData\Roaming
作为默认的 HOME 目录。对于 Windowx 2000 或 XP,Emacs 会将 C:\Documents and Settings\<用户名>\Application Data
作为 HOME 目录。对于……Windows 就是这样麻烦啊,倘若对此很烦躁,建议使用 Linux,或阅读 Emacs 文档中对 Windows 的 HOME 目录的说明 [1]。
对于 Linux 与 Mac OS X 而言,由于它们有点血缘关系,因此它们的 Emacs HOME 目录就是 $HOME
或 ~
,亦即操作系统的 HOME 目录。
函数
init.el 文件是 Emacs 的配置文件。现在,假定你已经能够在自己的系统中找到 Emacs 的 HOME 目录。接下来,就在这个目录中创建 .emacs.d 目录,然后使用 Emacs 在这个目录创建 init.el 文件。使用其他文本编辑器并非不可,但是不要浪费使用 Emacs 编辑文件的实践机会。
每当 Emacs 启动时,它都会读取 init.el,认真贯彻这个文件中的一些设定。因此,我们可以在这个文件中写一个 hello world 程序,让 Emacs 能够在缓冲区中显现 hello, world
。以后,这个文件就是我们在 Emacs 环境里的编程实验场地。
在开始写这个 hello world 程序之前,先体验一下如何通过 init.el 调整 Emacs 的外观。
在 init.el 文件中写入以下内容:
; 关闭菜单、工具栏、滚动条
(tool-bar-mode 0)
(menu-bar-mode 0)
(scroll-bar-mode 0)
然后重新启动 Emacs,发现它的菜单、工具栏以及滚动条都不见了。
上述内容,以 ;
开头的语句是 Emacs Lisp 的注释语句,会被 Emacs 忽略。之后的三行语句,对于 Emacs 而言,是三个程序,因为之前很认真地说过,Emacs 是计算机。init.el 中的内容,对于 Emacs 而言就是一组指令,而每条指令都可以视为一个程序。
由于我们通常所遇到的程序往往是由很复杂的代码构成。将这简短的三行代码视为三个程序,有煞有介事之嫌。那么,我们就将它们称为表达式。对于 Emacs 而言,init.el 中的每对小括号包含的文本,只要它不属于注释语句,对于 Emacs 而言都是表达式,要牢记这一点。
以 (tool-bar-mode 0)
为例,这个表达式可以让 Emacs 在启动时关闭工具条。倘若将这个表达式改为 (tool-bar-mode 1)
,那么下次开启 Emacs 的时候,工具条又会被打开。显然,是语句中的 0
或 1
决定着 Emacs 是关闭还是打开工具条。
假设你曾经认真学过中学数学,想必还记得函数吧?y = f(x),以映射的记法可写为 f: x -> y。倘若将 tool-bar-mode
视为 f,将 0
与 1
视为 x,将工具条的关闭与打开这两种状态视为 y,那么 (tool-bar-mode 0)
与 (tool-bar-mode 1)
在 Emacs 环境中所产生的作用,像不像 f: x -> y?
因此,像 tool-bar-mode
这样的事物,在 Emacs 里,是函数。上面在 init.el 中写入的三行语句,实际上是对三个函数进行求值。是谁在对函数进行求值,是 Emacs Lisp 解释器!
定义一个函数
接下来,我们可以在 init.el 中定义一个函数,即在 init.el 文件的尾部新开一行,然后写入以下内容:
(defun hello-world ()(interactive)(insert "hello, world"))
倘若你的确是按照我说的,使用 Emacs 编辑 init.el 文件,那么此刻只需使用 C-x C-s
便可将上述内容保存到 init.el 文件中,
现在关闭 Emacs,然后重新启动它,再重新打开刚才创建的 foo.txt 文件。这样 Emacs 便会重新读取 init.el 文件,但是这次它会读取上述语句。先不考虑 Emacs 对它们作何处置,现在,先在 Emacs 中尝试输入 M-x hello-world <RET>
命令,看看会缓冲区里会出现什么。这条命令里的 M-x
表示摁住 Alt
键,然后单击 x
,然后松开 Alt
键,接下来继续输入 hello-world
,然后单击回车键。由于在 Emacs 中,每当使用 C-x
或 M-x
时,Emacs 都会认为你要向它发出命令,所以它会使用微缓冲区接受你后续的输入。我的表述虽然有些冗长,但是倘若你真的动手尝试两三次,就自会明白这一切。
向 Emacs 发送 M-x hello-world <RET>
命令之后,结果会怎样呢?结果就是在 Emacs 当前的缓冲区里出现了 hello, world
。
这是我们向 Emacs 世界发出的第一声真正的问候。
函数的定义是表达式
对于 Emacs 而言,像
(defun hello-world ()(interactive)(insert "hello, world"))
这样的语句,虽然它似乎有些特殊——可以形成一条指令,但它依然是一个表达式。上文曾说过,在 init.el 中,每对小括号包含的文本,只要它不属于注释语句,对于 Emacs 而言,都是表达式。上述文本虽然比 (tool-bar-mode 0)
这样的表达式更复杂了一些,但由于它是以 (
开始又以 )
结尾,所以它是一个表达式。前文也说过,Emacs Lisp 解释器会对表达式进行求值。那么对于上述表达式,求值结果是什么呢?现在若不过于探究细节的话,不妨将这个求值结果视为在 Emacs 环境里定义了一个名为 hello-world
的函数,并且这个函数不接受任何参数。
不接受任何参数的函数,相当于一个常量函数,类似于 y = 1 这样的函数。因此,上述的 hello-world
函数是一个常量函数,函数值永远为 (insert "hello, world")
,这对于 Emacs 而言,又是一个表达式,而这个表达式是对 insert
这个函数进行求值。求值结果是什么?就是在缓冲区中出现 hello, world
,这就是 Emacs 对在响应 M-x hello-world <RET>
命令后,对 hello-world
函数的最终求值结果。
为什么 hello-world
的求值结果不是 (interactive)
呢?能在不清楚 (interactive)
这个表达式的含义的前提下提出这个问题的人,都是好同学。
对 hello-world
函数进行求值,本质上是对 hello-world
所包含的一组表达式进行求值。hello-world
包含了两个表达式:
(interactive)
(insert "hello, world")
它们构成了 hello-world
函数的实体。函数的实体与函数的名称通过 (defun ...)
表达式绑定到一起,这就是所谓的函数定义。
Emacs Lisp 解释器对 (defun ...)
这种表达式的求值逻辑较为特殊,它会顺序地对嵌入这一表达式中的子表达式按照先后进行排序,将最后一个表达式的求值结果作为函数的求值结果。
(interactive)
也是一个表达式,暂时不需要关心它对 Emacs 起到了什么作用—— Emacs Lisp 解释器对它的求值结果,由于它位于 (insert "hello, world")
之前,所以它的求值结果没有资格作为 hello-world
的求值结果。
公仆与民众
那么,(interactive)
的求值结果是什么?让 (defun ...)
定义的函数成为 Emacs 命令——可在微缓冲区执行的命令。
可以试着从 hello-world
函数中去掉 (interactive)
,即 hello-world
的定义变为:
(defun hello-world ()(insert "hello, world"))
然后保存 init.el 文件,再重新启动 Emacs,再重新打开刚才创建的 foo.txt 文件,然后再次执行 M-x hello-world <RET>
命令,就会发现,Emacs 根本不知道你在做什么。
因此,在 Emacs 的世界里,有两种函数,一种是可以作为命令执行的函数,它们就像通常所谓的程序一样,另一种则是普通的函数,它们默默无闻、甘于奉献,为那些可以作为命令使用的函数贡献了光和热。
重启不是必要的
在上文中,每次修改了 init.el 文件的内容之后,为了验证修改的结果,需要关闭 Emacs 再重新启动它,然后再次打开 foo.txt。这种操作过于繁琐,造成了人民日益增长的美好生活需要和不平衡不充分的发展之间的矛盾。
事实上,我们可以打开两个 Emacs 窗口,一个用于编辑 init.el,另一个用于体验 init.el 中的改动的效果,即用于编辑 foo.txt 文件。不妨前者称为「哼」窗口,将后者称为「哈」窗口。我们在「哼」窗口中所作的任何改动,保存到 init.el 文件中之后,在「哈」窗口中,只需执行 M-x load-file <RET> ~/.emacs.d/init.el <RET>
,便可让修改后的 init.el 在「哈」窗口中生效。这条命令中的 ~
,Emacs 会将其视为 HOME 目录路径的简写。
以后,随着对 Emacs 熟悉程度的增进,或者在阅读文档 [1] 之后,会发现打开两个 Emacs 窗口也是不必要的。
下一篇:勤劳,还是懒惰?
[1] 走近 Emacs
[2] Emacs 文档对 Windows 系统中的 HOME 目录的说明