Emacs Show – Editing

场景:一个同事在调Perl程序,通过注释程序的不同部分来定位问题所在。

问题:C/C++提供了/* */,用来注释大段代码,但是对于只有行注释的Shell脚本,Perl程序或者Emacs Lisp程序来说,这种调试方式似乎麻烦的很,特别是当行数较多的时候。

解决方案:Emacs,更准确的说——是各种语言的Mode,为用户提供了两条命令,快速注释和反注释选中的代码,他们是M-x comment-regionM-x uncomment-region。比如注释掉一个函数的操作序列通常是:

  1. C-M-h选中当前(即光标所在的)函数;
  2. M-x comment-region(在CC Mode里可以使用快捷键C-c C-c)注释掉整个函数。

你看,方便快捷,何乐而不为呢!如今Ruby越来越火,很多人提到的一个优点就是,它让程序员将精力完全集中在逻辑上,而不是实现的细节上。Ruby通过提供更高层的抽象实现了这一点。Emacs秉承同样的理念,程序员应该把更多的时间用在实现新功能或修改缺陷上,像在每行前面插入或删除井号(#,Perl、Shell等的行注释符)或分号(;,Emacs Lisp的行注释符)这样的活,还是交给程序来做吧。基于这样的理念,Emacs为用户提供了丰富的编辑操作。

在进行更多介绍之前,我们先解释上面例子中一些特殊符号的含义。C代表Control键,M代表Meta键(对于Windows键盘来说是Alt键),S代表Shift键;M-x的含义是先按住Meta键不放,再按x键,C-M-h的意思是同时按住Control键和Meta键不放,再按h键。使用Emacs时按Control键的机会非常多,但是对于中国人来说,Control键的位置似乎远了点,用小指按起来很累,我从同事那里学了一招——用手掌外侧去“压”Control键,这样,本来费力去按Control键的动作就转化成手轻轻向外侧翻的动作。如果你稍微留意的话,就会发现一个非常普遍的输入动作,左手按C-cC-x复制或剪切文本,右手握鼠标,定位光标后,左手再按C-v做粘贴。这是非常糟糕的编辑动作,是一个有追求的键盘手必须避免的。因为几乎每个人在按C-cC-xC-v时都要看一眼键盘,在把手从鼠标移回“j”键也要看键盘,结果,整个操作下来至少要看两次键盘,Cache狂miss啊,同胞们!更何况,在我们选择用手外侧这么优雅的动作去压Control键之后,就更不可能用一只手同时按C-cC-x了,在Emacs里,虽然它们不再代表复制和剪切,但同时按这两个键的机会还是很多的。因此,好习惯是右手按Control左手按cx,即使是对于M-x这种无伤大雅的击键,最好也养成右手按Meta左手按x的习惯。

Emacs的快捷键多如牛毛,对于熟练工来讲,它极大地提升了编辑效率,但对于初学者来说,则很难一下子记住那么多,没关系,我们一点一点来,先学习一些Emacs的基本概念

Point和Mark
Emacs里有两个特殊的位置。光标所在位置称为point,它可以通过Emacs Lisp函数(point)获得。有一点需要注意的,这个位置的概念与字符的位置概念是不一样,这里的位置指的是两个字符之间的位置,所以,所谓的光标位置,实际指的是光标前一个字符与当前字符之间的位置。理解了这里面的区别,就很清楚使用C-t交换两个字符位置时应该把光标放在哪里。比如我经常把label写成lable,这时,只要将光标停在le之间,然后按C-t就可以纠正拼写了。Mark是一个特殊的位置,Emacs通常为用户保存了一系列重要的位置,其中最新的一个称为当前Mark所在的位置,我们可以通过C-u C-Space将光标定位到当前Mark所在位置,还可以连续使用该快捷键回到前面任何一个保存的Mark位置。当然,这样的Mark通常是由一些命令自动设置的,比如粘贴(C-y)之后,光标通常定位在新文本的末尾,当我们想把光标移到原来的位置(即新文本的开头),就可以利用Mark,因为粘贴命令在操作前已经保存了一个Mark。用户也可以显示地申请一个Mark(C-Space),通常是为了选中某个区域(region)。
region
region就是选中的区域。选中一段文本的方式很简单,在你想选择的文本一端申请一个Mark,然后将光标移到另一端,这时Mark与Point之间的文本就称为选中的region。通常来讲,我们只能移动光标所在位置,所以当我们发现Mark错了位置时,只能放弃当前的选择,重新来过。Emacs知道用户经常会改变主意,所以提供了C-x C-x交换Point与Mark的位置,这样,我们就可以双向调整选中的区域。当我发现Emacs的这个功能时,就对自己说,这辈子就是它了,可见爱是多么冲动:-)。如果当前文本里存在一个选中的region,那么很多操作的行为会因此而发生改变,比如字符串替换。通常,它会替换从当前光标所在位置到文本末尾之间所有出现的字符串,但是当存在一个选中区域时,替换操作只发生在这个区域里。想想这有多方便吧,比如我想把一个函数里所有的变量i改名为index,那么只需用C-M-h选中这个函数,然后M-x replace-string。因为选中区域的操作非常频繁,所以我把调出输入法(无论是在Windows还是Linux上)的快捷键改为C-S-F9

好,我们现在可以学习一些Emacs为我们提供的便利工具了。很多编辑器都提供了整词查找和替换功能,是不是常方便?而Emacs不仅提供了更多的基于词的操作,还提供了基于句子,甚至基于函数的操作。各种各样的括号恐怕是最让程序员头疼的了,幸好很多编辑器都提供了括号匹配功能,而Emacs的括号匹配、光标移动以及region结合在一起,有意想不到的功效。让我们看看下面的一些例子吧。

Emacs里面的词指的是以空格或标点符号格开的字符和数字组成的序列。我们可以使用M-f向前(或M-b向后)将光标移动到相邻单词的边界,使用M-@将Mark设在下一个单词的边界处,而保持光标位置不变,M-d删除下一个单词,M-Backspace向后(backward)删除一个单词(不用再狂按Backspace了)。另外,当编写程序时,我们还可以使用C-M-a将光标定位到当前函数头,C-M-e到函数尾,用C-M-f选中当前函数。而C-M-nC-M-p则可以将光标定位在两个匹配的括号上。另外,Emacs也提供了相应的快捷键取代HomeEndPageUpPageDown,使得我们可以彻底抛弃这些键,以及方向键和鼠标。综合使用Emacs提供的丰富的光标移动操作,我们定位光标的速度将远远大于过去那种鼠标加方向键的模式。这些操作不仅可以用来移动光标,还可以用来选中区域。比如选择一对大括号包围的所有语句,只需在左大括号前申请一个Mark,然后用C-M-n将光标移动到对应的右大括号之后,这样,整个选择操作就完成了。

怎么样?有点动心了没有?

Emacs Show — Motivation

“工欲善其事,必先利其器”。用Emacs三年,略知其中滋味,希望与更多的程序员分享,也算为开源社区做点贡献吧。

学了Emacs才知道,输入(以及修改)程序也是有很多学问的。记得当初关于“软件蓝领”的争论焦点就是写程序是体力活还是脑力活,是否仅仅等于敲敲键盘。虽然到头来双方仍是自说自话,但似乎都同意“敲键盘是体力活”。大家对编辑器的期望无非也就是语法高亮、自动缩进,如果能有自动补全那就谢天谢地了。我周围的同事用的编辑器各种各样,有SourceInsight、SlickEdit、UltraEdit……,但用得最多的还是vi,甚至连新来的实习生都用vi,这让我很吃惊,我上学那会儿可都是用IDE的啊。即使这样,还是有很多同事对UltraEdit的列插入功能津津乐道,由此可见,很多编辑器对于编辑这个老本行做得还不够。大家使用SourceInsight和SlickEdit,也是用其代码浏览和定位功能,而其编辑功能并无出彩之处。

编辑是程序开发领域唯一的体力活吗?体力活意味着机械、单调、令人厌烦。我们选择干程序员这行,不就是想多动脑少动手吗?如果你把编辑当成体力活,那么肯定你的手指经常游走在方向键、Home、End、PageUp、PageDown、Delete和Backspace之间,偶尔(也许是很多时候)不得不借助鼠标,即使你也知道用鼠标对手腕的伤害要远远大于键盘。也许你觉得这没什么不好,难道不是每个程序员都这样吗?答案是:不是。Emacs用户将这种行为比喻为Cache Miss,可见其对效率的伤害程度。几乎每个人都会同意,输入速度取决于盲打的熟练程度,看键盘是非常忌讳的。然而,有多少人可以不看键盘就能找对上面提到的几个键呢?即使找对,恐怕手已移开半尺(鼠标就更远了),让食指回到”J”键上也不那么容易吧。当我们尚不清楚接下来该写什么的时候,这并不成问题,尽可以将每个动作做得轻松、优雅。但是当我们早已胸有成竹,只等下手时,还是这样编辑,不仅减缓速度,还会滋生急躁、甚至烦躁的情绪。试想以下操作我们通常是如何完成的:

  • 删除一个长达三屏(甚至更长)的函数;
  • 重新格式化一段没有做好缩进的代码;
  • 只在一个函数(或一块文本)里做字符串替换;
  • 选好一块很长(数屏)的文本后发现结尾处选对了,可是开头处要做一些调整。

我曾经为这些问题郁闷得不行,尤其是最后一个,简直暴跳如雷,因为除了重来,还能怎样呢?直到用了Emacs才发现,原来生活可以如此简单,写程序可以如此快乐。所以,尽管过了三年,我仍然为每一次击键兴奋不已,我所在的组共6人(包括我),经过我的鼓吹,有3名同事开始使用Emacs。

使用Emacs的好处有很多,我会在后续的blog里面重点介绍,但我认为最重要的是,它彻底改变了我对编辑的认识,从此以更严肃的态度对待它。正如高级语言取代汇编语言一样,那些只能区分单个字符的编辑器也该被丢弃,只有提供更高的抽象,以及基于这些抽象的操作,才能使开发过程更加高效。

我个人认为Emacs的强大之处可以在下面几个方面得到体现:

  • 对编辑操作的支持极其丰富,使得输入过程非常流畅,比如支持正反双向以一个字符、单词、句子、甚至段落为粒度移动光标,将光标快速定位到函数头尾、该行第一个非空格字符,双向调节选中区域,注释掉选中区域或去掉选中区域的注释(对于只支持行注释的语言,如Perl等,非常方便),等等,数不胜数;
  • 可定制性。每个人都有自己的偏好,有人喜欢语法高亮,有人不喜欢;有人喜欢4格缩进,有人喜欢GNU风格,还有人不希望缩进有tab键,只能用空格,等等。Emacs为此提供了极其灵活的定制功能,使其满足不同口味的人群。
  • 可扩展性。Emacs由两种语言实现——C和Emacs Lisp,除了少数基本操作和一部分对性能要求较高的函数外,绝大多数功能都是由Emacs Lisp实现,而Emacs本身就可以看作是一个Emacs Lisp程序运行环境,因此,任何人都可以使用Emacs Lisp实现自己所需的功能。同样是编辑,写程序和写文章的需求是不一样的,正是由于Emacs的这种开放性,使其可以满足绝大多数编辑需求,即使是那些非常古怪的要求,因为你总是可以写一段程序来完成它。不要因为看到Lisp就畏缩不前,它有很多美妙的语法特性是你在其它语言中看不到的,它让你惊呼,原来程序也可以这么写。
  • 在线帮助。没有比找不到帮助更让你泄气的了,而Emacs永远不会让这种事情发生。所以,你很难在市面上见到讲Emacs或Emacs Lisp的书,而网站只需http://www.emacswiki.org/一个足矣。
  • 与环境完美集成。这个环境当然是指GNU/Linux环境,在Windows上可以选择Cygwin。与版本控制系统的无缝连接,编译、调试、搜索,收发电子邮件,订阅新闻组,查看其它GNU/Linux系统命令帮助,等等。一种夸张的说法是有人就生活在Emacs里。:-)

学习Emacs的唯一难处是开始很难。原来一个美国同事说他从vi转到Emacs时有6个月啥也不能干。我想可能是夸张了点,一些基本操作还是很快就能上手,毕竟,你还是可以像使用notepad一样使用它。难的是改掉陋习,和记住多如牛毛的快捷键。需要彻底地洗脑,这是一个漫长的过程,要吾日三省吾身,多学习多温习多看帮助多练习。

我知道,仅凭一篇文章很难带来足够的动力,我会继续写几篇,展示Emacs的迷人之处,也希望它能成为每个中国程序员手中的利器。