Emacs:自由软件 IDE
Emacs 以编辑器而闻名,但称 Emacs 为编辑器就像称 玛丽女王号 为小船。Emacs 21.1 的源代码 RPM 大约有 20MB——对于编辑器来说非常庞大。您不必使用它的全部功能,但有时知道它在那里也很好。
Emacs 具有高度可定制性,这使其非常灵活。例如,我正在使用 Emacs 撰写本文,并将使用 Emacs 进行拼写检查。“好吧”,我听到您说,“如果它擅长撰写文章,那么它就不太擅长开发软件。” 这正是 Emacs 的灵活性和可定制性发挥作用的地方;您可以将其用于其中任何一种,或用于完全不同的用途。想去看心理医生、玩 俄罗斯方块 或在玛雅日历中操作日期吗?Emacs 可以做到。
Emacs 的定制功能以包的形式出现,称为模式。主模式设置 Emacs 以执行特定的操作,例如 C 模式用于编辑 C 和 C++ 源代码,或 psgml 模式 (www.lysator.liu.se/projects/about_psgml.html) 用于在您编写程序后编辑文档。次要定制为主要模式提供额外的功能。显示括号模式显示匹配的括号,自动填充模式允许您输入填充文本而无需显式输入换行符。大多数模式都有变量来控制其行为,您可以临时或永久地修改它们。
大多数用户将其自定义设置存储在 ~/.emacs 中,称为“dot Emacs”文件。对于好奇的人,我的文件在 Web 上:w3.trib.com/~ccurley/emacs.init.html。其他文件可以在“非常非官方的 .emacs 主页”找到:www.dotemacs.de。
本文介绍 GNU Emacs 21.1 版本 (www.fsf.org/software/emacs/emacs.html)。XEmacs (www.xemacs.org) 也可在大多数 Linux 发行版上使用,并且可能会执行我在此处描述的所有操作。
为了展示 Emacs 作为一个集成开发环境 (IDE) 的功能,我将演示一个简短的 C 程序的开发过程,并向您展示如何操作。我们将编写著名的 Hello World 程序,这个程序被各地的 C 程序员所熟知和喜爱。
虽然本文展示了 C 开发,但 Emacs 也支持其他语言,包括 C++、汇编、Scheme、Java、Ada、IDL、Makefiles、Lisp(包括 Emacs Lisp,Emacs 的大部分代码都是用这种语言编写的)和 FORTRAN。而这仅仅是 Emacs 自带的功能。附加软件包比比皆是;甚至还有一个用于 Forth 的软件包。
此外,您可以将 Emacs 用于许多其他类型的文件。当需要编写程序文档时,您可以使用 Emacs 的 psgml 模式来更轻松地编辑文档源。Emacs 也提供了 HTML 和 TeX 模式。
我们做的第一件事是使用 Emacs 的 dired 模式创建一个目录来存放程序。在 dired 模式下,我们键入 +,然后在编辑器底部的 minibuffer 中输入要创建的目录的名称(图 1)。按回车键,Emacs 就会为我们创建目录。
接下来我们进入该目录,然后使用访问文件的常用快捷键 Ctrl-X Ctrl-F 创建文件 hello.c。在 minibuffer 中键入文件名 hello.c,然后按回车键。现在我们看到 Emacs 准备好了一个新的缓冲区来编写我们的程序。
任何 C 程序员在打开新文件时应该做的第一件事是输入注释来说明该文件的作用。因此,我们按下 M-;(M- 代表 meta 键,或大多数 PC 键盘上的 Alt 键),得到一个 C++ 风格的注释分隔符,光标准备好接受我们的注释。我得到 C++ 注释的原因是因为我已自定义 Emacs,以便在 C 语言中使用 C++ 模式。您可能会得到 C 风格的注释分隔符 /* */,光标在中间准备好让您输入注释。如果您更喜欢使用 C++ 注释,请执行 M-X C++-mode。
然后我们添加一个时间戳,所有这些都由 Emacs 的 timestamp minor mode 定义。同样,这是我自定义的内容;您可能无法使其工作。
一个 C 程序必须有一个 main 函数,因此我们以 GNU 风格输入它:返回类型在一行,函数声明在下一行。请注意,在我们键入时,语法高亮会生效(图 2)。

图 2. 在 Emacs 中输入 main 函数的声明。请注意语法高亮和括号匹配。
现在介绍 Emacs 最有趣的功能之一:Electric C。按下某些字符,Emacs 会为我们处理缩进和美化打印。完成函数声明后,在普通编辑器中,人们会按回车键,键入左大括号 ({),然后按回车键和 Tab 键。但在 Emacs 中,只需键入左大括号,Emacs 就会完成其余操作。(如果第一次不起作用,请使用 Ctrl-_ 撤消大括号,然后按 Ctrl-C Ctrl-A 切换电气化。)
现在我们输入一行代码。键入 printf,然后输入一个空格。现在是时候输入一个括号了。同时,为了保持括号平衡,也需要一个闭括号。因此,按下 M-(,即 PC 键盘上的 Alt-(。Emacs 会插入一对括号,并向后移动一个字符,以便我们可以在内部键入代码。然后,我们键入 "Hello, wolrd.\n",包括拼写错误(稍后我们会回到这个问题)。
我们没有按回车键,而是输入了另一个 electric 字符 Ctrl-J。细心的读者已经注意到我们遗漏了一个分号。请注意会发生什么。Ctrl-J 应该 将我们向下移动一行并缩进到“printf”中的“p”正下方。但在这种情况下,它没有这样做。Emacs 假设我们是从上一行继续,因此它会进一步缩进。即使对于一个只喝了当天第一杯咖啡的软件工程师来说,这也应该是一个警报,表明有些地方出错了。是的,我们确实遗漏了分号。我们使用 Ctrl-Shift--(即 Control-Shift-Hyphen)撤消 Ctrl-J,然后键入分号。嘿!分号也是 electric 的!但是请注意 Emacs 是如何防止错误的发生的?这更酷!
现在,当然,是时候输入右大括号以结束 main 函数了。由于我们的 electric 分号为我们进行了缩进,我们是否需要退格回到左列?不,只需键入右大括号即可。
现在我们已经编写了程序,我们保存它 (Ctrl-X Ctrl-S) 并运行测试编译。由于这是一个单源文件程序,我们不需要 make 文件。(但是有一个 make 文件模式,我将留给学生作为练习。)相反,我们从 Emacs 内部编译,M-X compile。这将生成一个建议的编译命令行,在本例中为默认的 make -k。由于我们没有 make 文件,我们使用向下箭头键并编写我们自己的编译命令
gcc -g -o hello hello.c
-g 选项编译 GDB(GNU 调试器)的信息,-o 选项告诉链接器输出文件将被命名为“hello”。令我们惊讶的是,我们得到了完美的编译(图 3)。

图 3. 在 Emacs 中成功编译
现在来测试它。我们鼓起勇气——或者说幸运——使用 M-X shell 在 Emacs 下启动一个 shell 并执行程序(图 4)。但是,有些不太对劲。我们拼错了“world”。糟糕。

图 4. 从 Emacs 内的 Shell 执行程序
但与其仅仅纠正这个错误,我们可以对我们的注释和字符串进行拼写检查。为此,我们使用 Ctrl-X Ctrl-B 回车键返回到源代码缓冲区。现在可以使用下拉菜单(Tools—>Spell-Checking—>Spell-Check Comments)或执行 M-X ispell-comments-and-strings(现在是查看 Tab 补全侧边栏的好时机)。我们按空格键绕过 IDE,按 0 接受 world 而不是 wolrd(图 5)。然后按 Ctrl-X Ctrl-X 保存我们的更改。

图 5. 在 Emacs 中拼写检查源代码
现在我们已经完成了这些,是时候再次编译了。与 Bash 一样,Emacs 会记住我们的命令历史记录,因此 M-X 后跟一个或多个向上箭头键会将我们带回到编译命令。按回车键。Emacs 向我们显示了我们上次的编译命令行。它看起来不错,所以再次按回车键。
Emacs 也是 GDB 的前端。要进入 GDB 模式,请执行 M-X gdb。Emacs 会弹出一个建议的命令行“gdb”。我们添加要调试的可执行文件的名称 hello,然后按回车键。这会将我们带入 gdb 模式,gdb 提示符已准备好操作。我们使用 gdb 命令 break main 在函数“main”处设置断点。(Tab 补全在这里也有效。)然后键入 run 以运行程序到我们的第一个断点。
GDB 向我们显示程序的输入值。但是,Emacs 已将另一个窗口设置为显示源代码。当我们单步执行程序时,Emacs 将在源代码窗口中使用箭头指示要执行的下一行。我们甚至可以切换缓冲区 (Ctrl-X Ctrl-O) 并根据需要编辑源代码。按 N 执行一条语句。在本例中,该语句生成输出,我们在 GDB 窗口中看到该输出。再按一次 N,GDB 会通知我们程序已退出,返回值为,嗯,垃圾。
我们记得,程序成功完成后通常应返回零。我们应该将其添加到程序中。因此,我们退出 GDB 并切换到源代码窗口。光标正好位于程序最后一个大括号的前面。要添加返回代码,我们输入 return (0)。
在我们的演示中,我们不会做得非常花哨。但是,对于比我们在此处所做的更复杂的事情,在到达程序执行的有趣部分之前,您会发现很多重复。因此,您可以为 GDB 准备一个 init 文件,该文件是 GDB 命令的列表,这些命令按照 GDB 在文件中遇到的顺序执行。因此,您可以自动化 GDB 可以做的任何事情——这相当多。
由于匆忙,我们忽略了分号,按了回车键而不是 Ctrl-J。尝试编译。执行 M-X 和向上箭头键,直到 minibuffer 中出现“compile”,然后按回车键。命令行没问题,所以再次按回车键。
糟糕!我们出错了。要定位源代码中的错误,我们按 Ctrl-X `。这会将第一个错误消息放在编译输出缓冲区的顶部,并将光标放在错误行上。错误位于大括号之前,因此我们查看上一行的末尾。太糟糕了;我们犯了经典的新手程序员错误,遗漏了分号。因此,我们按向左箭头键并添加分号。然后我们使用 Ctrl-X Ctrl-S 保存我们的工作。
Emacs 的编译模式设置为检测来自大量编译器(例如 Microsoft Visual C++)的错误消息,而不仅仅是 C 编译器。Emacs 还识别来自 weblint(HTML 检查器)的输出。
请注意,electric 分号做了两件事:它为我们缩进了行,这很好,但它还在“return”语句和右大括号之间插入了一行,将光标定位在准备添加另一行的位置。好吧,这不是我们想要的。因此,我们使用 M-Shift-- 撤消它。现在我们覆盖 electric 分号,并使用 Ctrl-Q ; 插入分号。
如果程序的所有内容都正确缩进,那就太好了,因此我们使用 Emacs 的缩进工具。首先,我们选择要缩进的区域。要选择整个程序,请按 Ctrl-X H。然后我们使用 Ctrl-M-\(即 Control meta-backslash)进行缩进。要查看结果,我们使用 Ctrl-X 1 隐藏编译缓冲区,并欣赏我们的手工作品(图 6)。完成后,我们再次编译(M-X compile 回车 回车)。这次我们成功了。

图 6. 使用 Emacs 进行美化打印
当然,任何程序员都知道要大量注释他们的代码。首先,我们需要一个通用注释来说明程序的作用。因此,我们在时间戳之后立即在源文件中输入该注释(图 7)。从 Emacs 的开始注释命令 M-; 开始。请注意,在您键入时,文本只是简单地换行(如左右边距中的箭头所示)。这很难看,因此我们按 M-Q 来“填充”段落。请注意,C++ 注释分隔符已插入到每行的开头(图 8)。

图 7. 在 Emacs 中输入注释(或其他文本)

图 8. 相同的注释,现在“已填充”
我们可以安排使用 M-X auto-fill-mode 自动填充文本,但这也会填充我们的 C 源代码,这不是我们想要的。如果您愿意,可以为注释打开自动填充模式,然后为源代码再次关闭它。
您还可以注释一行代码。将光标放在行上的任意位置,按 M-; 并继续。Emacs 将在行尾插入注释分隔符,并将光标移动到输入注释的适当位置。对于 C++ 风格的注释,这将是行尾,但对于 C 风格的注释,则在分隔符之间,/* ... */。
并且,不要忘记使用 M-X ispell-comments-and-strings 对您的注释进行拼写检查。
Emacs 有一个用于 CVS、RCS 或 SCCS 的前端,称为 VC。前两者是自由软件版本控制系统,并且随附于许多 Linux 发行版。VC 非常智能,可以确定您正在使用哪个系统。大多数 VC 功能通过快捷键 Ctrl-X Ctrl-Q 或 Ctrl-X V V 处理。根据文件的当前状态,VC 将检入或检出文件。如果文件从未添加到版本控制系统,Emacs 将确定这一点并为您检入文件。当您检入更改时,Emacs 将为更改注释创建一个临时缓冲区,因此您没有任何理由忽略这个良好的编程习惯。
此前端适用于任何受版本控制的文档。例如,Linux 文档项目使用远程 CVS 来控制他们的 linuxdoc 和 docbook 源文件。即使我通过 56KBps 拨号线路进行贡献,我也可以使用 Emacs 使 CVS 过程透明化。
标签是一个函数数据库,可选地,typedef 名称是使用程序 etags 创建并存储在 TAGS 文件中的。它们与其位置一起存储,从而可以轻松检查函数的定义。要为 C 程序创建标签文件,请从任何 shell 运行 etags -t *.[ch]。您可以将标签用于多种编程语言;从 shell 运行 etags --help 以获取列表。
我们最小的程序只有一个文件和一个函数。其他开发人员应该如此幸运;许多 C 项目分布在多个目录中的多个文件中。标签也将涵盖像这样的复杂情况。实际上,项目越复杂,标签就越有用。
要检查函数的源代码,请使用 M-.(即 meta-period)。开始键入函数名称并使用 Tab 补全。如果您尚未指定要使用的标签文件,Emacs 将会询问。通常,默认值正是您想要的(图 9)。

图 9. 使用标签工具定位函数
请注意,默认函数名称是源代码窗口中光标旁边的单词。标签模式允许您编辑一个文件,将光标放在要检查的函数名称上,然后通过最少的按键操作编辑该函数。
您还可以在另一个窗口中显示函数的源代码,使用 Ctrl-X 4 .. 这使您可以同时检查多个函数,数量取决于您可以在屏幕上容纳多少个函数。如果要查看对函数的所有引用,请使用 M-X tags-search。要继续搜索,请使用 M-,。
此外,标签对于在项目中搜索和替换函数名称非常有用。例如,如果我有一个名为 Frodo 的函数,并且我想将其重命名为 Gollum,我不仅需要更改函数声明,还必须获取每个原型和每个引用。因此,我使用 M-X tags-query-replace 并开始操作。这两个搜索功能都使用正则表达式,这使它们非常强大。您可以使用 Ebrowse 浏览 C++ 类。
文件比较是比较两个文件,例如一个源文件的两个版本。大多数程序员都熟悉 diff 和 patch。Emacs 为 diff 和 patch 提供了强大的前端。首先,Emacs 的 ediff 模式在编辑器中将两个(或三个)文件上下显示。差异在任何能够显示颜色的显示器上都会突出显示(图 10)。整行都会被指示,精确的差异会用不同的颜色指示。

图 10. 显示两个文件之间的差异
不仅如此,您还可以使用单字符命令将差异从一个文件移动到另一个文件;a 或 b 分别将 A 或 B 缓冲区中的行移动到另一个缓冲区。这允许您遍历两个文件,比较它们并接受或拒绝每个差异。
要一次单步执行两个文件中的一个差异,请将 ediff 控制面板置于焦点。使用空格键前进,使用 P 键后退(图 11)。Ediff 模式可以跟上您即时进行的更改。尝试在 goodbye.c 中的 printf 行之后添加一行,调用 flush();。

图 11. Emacs Ediff 模式的控制面板
Emacs 有一个庞大的内置帮助库。Emacs 帮助的入口是 Ctrl-H。如果您完全不熟悉 Emacs,请从教程开始,Ctrl-H T。如果您已经熟悉 Emacs,您也应该浏览一下教程;总有一些关于 Emacs 的东西可以学习。
如果您熟悉 GNU 程序 Info,您就已经知道如何使用 Emacs 的帮助系统。Ctrl-H I 将您带到 Info 系统的顶部菜单。从那里,Memacs 将您带到 Emacs 文档。当然,Info 的文档也可以从顶层 Info 菜单中获得。
如果您想要其他程序或 Linux 函数调用的文档,您可以使用 Emacs 作为 Info 阅读器。或者,您可以从 Emacs 中读取 man pages。您可以使用 Emacs 作为 Man 的前端,命令为 M-X manual-entry。或者,您可以使用 woman (WithOut MAN) 包让 Emacs 解释 man page 并为您显示它。由于 Windows 的 Cygwin 工具 (www.cygwin.com) 包含 man pages,但没有程序来读取它们,因此 woman 正是您需要的。
例如,要查看 printf 的 man page,请将光标放在源代码中的单词 printf 上。输入 M-X woman 并按回车键。Emacs 将建议 printf 作为默认的手册条目。有两个 printf man pages,一个在 man 3 中,另一个在 man 1 中。通过按 3 Tab 告诉 Emacs 您想要 man 3。Emacs 将完成文件名,然后按回车键。Emacs 将向您显示 printf 的 man page。
电子邮件:ccurley@trib.com
Charles Curley (w3.trib.com/~ccurley) 居住在怀俄明州。他拥有 23 年的计算机经验,其中大部分是作为软件开发人员。他曾在休斯飞机公司、惠普公司、微软公司和喷气推进实验室工作。他为 Sams 的 24 小时自学 Emacs (ISBN: 0-672-31594-7) 做出了贡献。