让 Emacs 为您工作,第 1 部分

定制和集成你的 Emacs 环境

Comments

20 世纪 70 年代的时候,一些神奇的科学家在 MIT 人工智能实验室 (AI Lab) 开发了一个神奇的编辑工具,它被叫作 Emacs(The extensible customizable self-documenting display editor)。其中一位就是著名的自由软件基金会 (FSF) 创始人 Richard Stallman。想要了解更多 Richard 和他的自由软件的读者可以去读他写的 Free as in Freedom

今天我们讨论的是一个 已经 30 多年的历史的编辑工具。在计算机软件这个领域,任何一个工具要想在这么长的时间里长盛不衰,永葆青春,都不是一件容易的事情。为什么 Emacs 能够做到呢?我也无法给出一个确切的答案。毕竟我也不是 Emacs 这个领域的专家。但是我认为,Emacs 充满人性化的细节设计一定是其中不可或缺的重要因素。

下面就让我们通过一些简单的实例,来与大家共同体验 Emacs 带来我们的快乐。下面的这些内容适合于 Emacs 的中级用户。这些用户已经熟练地掌握了 Emacs 的日常操作,并且理解 Emacs 可以使用 Emacs Lisp 语言进行定制与扩展的。对于没有接触过 Emacs 或者刚刚接触 Emacs 的读者,可以首先阅读 developerWorks 的教程《生活在 Emacs 中》。

让拷贝和粘贴变得充满乐趣

前段时间 Discovery Channel 制作了一组赢得艾美奖的电视节目,说到人们生活中的种种脏活累活。Discovery Channel 因为关注了这些不被大多数人所注意的生活细节,赢得了艾美奖的荣誉。同样的,我们这里所要讨论的也是文本编辑过程中的不为大多数人所关注的“脏活”和“累活”。这些生活细节虽然不为人们所关注,但是又谁也离不开它。

我来举这样一个例子。这是我们每天最常进行的操作——拷贝和粘贴。准确地说,应该是 拷贝 。粘贴很简单,我们大家都会喜欢做。尤其是在 Emacs 里面, 粘贴是一件充满乐趣的事情 。因为在 Emacs 里面我们可以利用可爱的“ 删除环 (kill-ring)”机制,随心所欲的进行粘贴。

但是说到拷贝,那可就是另一回事情了。也许你还没有注意到,每一个拷贝操作总要伴随着一个非常繁琐的准备工作,那就是你必须选中,或者说标记出所有要拷贝的内容。而且是每一次,每一次,精确地,标记出来。这个就是我所认为的,既没有技术含量,又很繁琐,每天不断重复,又脏又累的,文本编辑当中的“脏活”“累活”。标记工作完成之后,ESC-w,……这两个按键谁都会按。

那么为什么我们不能够摆脱这些繁琐的工作呢?为什么我们不能端着一杯香浓的乌龙茶,为什么我们的编辑器不能够替我们去操心这些事情呢?计算机技术发展这么多年了,为什么拷贝不能够变得像粘贴一样的充满乐趣呢?

问得好!事实上, 我们可以! 你只需进行拷贝,剩下的交给 Emacs

要实现上面的这些要求,我们需要对 Emacs 进行一点小小的扩展,一点非常简单的扩展。 让我们来看一看下面这段代码。

(defun copy-line (&optional arg)
 "Save current line into Kill-Ring without mark the line"
 (interactive "P")
 (let ((beg (line-beginning-position)) 
	(end (line-end-position arg)))
 (copy-region-as-kill beg end))
)


(defun copy-word (&optional arg)
 "Copy words at point"
 (interactive "P")
 (let ((beg (progn (if (looking-back "[a-zA-Z0-9]" 1) (backward-word 1)) (point))) 
	(end (progn (forward-word arg) (point))))
 (copy-region-as-kill beg end))
)


(defun copy-paragraph (&optional arg)
 "Copy paragraphes at point"
 (interactive "P")
 (let ((beg (progn (backward-paragraph 1) (point))) 
	(end (progn (forward-paragraph arg) (point))))
 (copy-region-as-kill beg end))
))

这段代码定义了三个 Emacs Lisp 函数。这三个函数提供了可以在 Emacs 编辑器中执行的三个操作。分别用来拷贝当前光标所在的 ,当前光标所在的 单词 以及当前光标所在的 段落 。是不是非常简单?基本上这就是让 Emacs 替你操心选中和标记拷贝内容所需要的一切了。 现在 你就可以拷贝这些代码,把他们粘贴到你的 .emacs 配置文件里面。然后重新启动 Emacs, 或者使用 ESC-x 组合键,在提示缓冲区里面输入 eval-buffer 命令,使这些新增加的功能在你的 Emacs 里面生效。

接下来我们就可以试试看,看看这些代码究竟效果如何。请你打开一篇文档。为了方便起见,我们就用上面这篇代码作为测试文档。

现在我们要尝试拷贝 "Save current line into Kill-Ring without mark the line " 这一行文本。你不需要去标记或选中它。请 试着把你的光标移动到这行文本的 Kill-Ring 这个词组上面,按下 ESC-x 组合键,然后在提示缓冲区里面输入 copy-line 命令。猜猜看,什么事情发生了?对了,你已经成功地将 "Save current line into Kill-Ring without mark the line " 这行文本拷贝到了 Emacs 的 kill-ring 当中去了。并且在这个过程当中,我们并不在乎你的光标当前位于这行文本当中的任何一个位置。即使是在行尾,你依然能够成功拷贝。

为了验证刚刚完成的拷贝工作,你可以在编辑缓冲区的任何地方,按下 Ctrl-y 组合键,将刚刚拷贝出来的 "Save current line into Kill-Ring without mark the line " 重新粘贴回去。 下面我们尝试拷贝单词。首先将你的光标移动到刚才那个文本行当中的 without 这个单词上面。你可以采用输入 ESC-b 组合键的方法,或者使用任何你喜欢的方法。按下 ESC-x 组合键,在提示缓冲区里面输入 copy-word 命令,相信我,你已经成功地,不多不少地,正好将 without 这个单词拷贝到了 Emacs 的 kill-ring 当中去了。同样的,你可以使用 Ctrl-y 组合键来验证它。

最后一步,我们就要尝试激动人心的整段拷贝操作了。调节一下呼吸。选择一种你喜欢的方式,移动光标到“Copy words at point into kill-ring”这一行。小心翼翼的按下 ESC-x 组合键,再提示缓冲区当中输入 copy-paragraph 命令。

现在先不要松劲儿。赶紧移动你的光标,找到一片空白的编辑缓冲区。输入 Ctrl-y。一个完整的代码段落就在你的光标位置出现了。那就是你刚刚拷贝的那个段落。

是不是可以开香槟庆祝了?事实上,精彩的地方还远不止这些。正如你所想象的,这里所有的三个函数统统都支持 Emacs 功能强大的 Ctrl-u 命令前缀。你只需在输入命令之前首先按下 Ctrl-u 组合键,然后输入一个数字,例如 4,然后输入 copy-line,copy-word 或者 copy-paragraph 命令,你就可以任意拷贝你所希望的数量的行,单词和段落。 这不是魔术。这只是在 Emacs 带给我们的一小部分快乐。下面我们就来探索一下这些“神奇”的事情究竟是怎么实现的。事实上非常简单,这里的每一个函数都只有 6 行代码。这些代码都可以用自然语言的方式来阅读和理解。那么如果我们希望能够更进一步的了解这些代码里面的细节,又该怎么样呢?大家一定还记得我曾经提到过,Emacs 的设计是非常人性化的。其中的一个人性化的细节就是 Emacs 自身就携带了丰富的文档。我们只需要按下 ESC-x 组合键,在提示缓冲区输入 describe-function 命令,然后在 Describe function: 提示符后面输入想要了解的函数名称。例如 interactive

然后就可以在屏幕的下方看到一个帮助缓冲区,缓冲区里的内容就是对 interactive 函数的具体描述。

同时,我们也可以使用 Ctrl-h f 组合键来调出 describe-function 命令。有兴趣的读者可以使用 describe-function 命令逐一了解这些代码里面的每一个函数。 只有一个需要特别说明的地方,就是 copy-word 函数比其他两个函数多出了一段 if 语句。那么这个 if 语句又是做什么用的呢? 这里面有一个细节问题。在大多数情况之下,没有这段 if 语句,我们的函数也将完美的完成我们希望的任务。但是如果在我们执行操作之前,光标恰好位于一个单词的首字母之上,例如 without 单词的 w 字母之上的时候,就会发生一些意想不到情况了。(backward-word 1) 这条语句所做的操作是将当前光标向后移动到它所遇到的第 1 个单词首字母位置。因为 backward-word 函数并不会理会当前光标位于什么位置,他只是无条件的 向后 寻找满足条件的下一个首字母位置。那么在这个时候,被找到的就会是 Ring 这个单词的 R 字母。那么,copy-word 拷贝出来的就将不再是 without 而是 Ring 这个单词了。这显然不是我们所希望发生的事情。而且更加不幸的是,当我们使用 ESC-b 组合键在 Emacs 当中移动的时候,光标的位置恰恰就是位于每一个单词的首字母位置。显然这不是 Emacs 的问题。这是我们的问题。解决起来也很简单。就是在移动光标之前首先进行一个判断。如果光标当前恰好位于一个单词的首字母之上,我们就无需进行移动,直接执行 point 函数返回光标位置就行了。 事实上同样的问题也会发生在 copy-paragraph 函数的身上。因为 copy-paragraph 函数和 copy-word 函数使用的是相似的移动光标的机制。但是在我的代码里面并没有为 copy-paragraph 函数添加类似的 if 语句。原因是这样的。对于通常包含大量文本的段落来说,光标位置落在段落内部的概率要远大于恰好落在段落开始位置的概率 ( 当然,ESC-{ 组合键会帮你做到这一点。 :-p )。因此我选择不去添加这段 if 语句。这样可以最大限度的保持代码的简单性。有兴趣的读者可以试着自己去添加 copy-paragraph 当中所需要的 if 语句。

结束语

在本文当中,我们介绍了一组非常简单的 Emacs 扩展。但是这些扩展虽然简单但却彻底改变了文本编辑器的使用方式。通常情况下,文本编辑器所做的工作就是接受我们的输入,并把这些输入按照指定的样式显示出来。剩下的事情都得由使用者负责考虑。换句话说文本编辑器的工作是非常轻松的。疲惫的是我们这些使用者。所以很多时候我都很希望能和我的文本编辑器互换一下我们的工作。直到有一天我选择不再忍受了。我选择让我的 Emacs 为我工作。我选择让 Emacs 去操心所有的编辑细节而我只需要考虑真正有价值的事情,也就是编辑的内容。有一天我把这个想法告诉了我的 Emacs,你们猜她说什么?


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=290110
ArticleTitle=让 Emacs 为您工作,第 1 部分
publish-date=02182008