分类: LINUX
2007-04-30 17:02:43
Emacs 编辑器完全介绍(8)
移动光标
基本操作。
文档一般都有各种结构,比如LISP里有S表达式,C语言里的函数,LaTeX 里的 \begin{...}...\end{...} ... 如果我们能够已文档的语法单位来移动,就会使操作非常高效。
1. defun。defun 在 LISP 里就是最高一级的 sexp,而在 C 语言里,它的含义就是函数。
2. C-M-a 到 defun 头
3. C-M-e 到 defun 尾
这样,我们在C语言程序里可以一个函数一个函数的跳过。也可以从一个函数中间一下跳到函数开头或末尾。
4. 语法单位(sexp)。语法单位在各种 major mode 有不同的定义:
所以在 Emacs 里,寻找匹配的括号可以在括号处使用 C-M-f 和 C-M-b.
其实 defun 可以被看作最高一级的 sexp。所以范围小一点的移动操作就是在同级 sexp 之间移动。
C-M-f 到下一个同级语法结构
C-M-b 到上一个同级语法结构
注意,这种移动不能越过语法结构的边界而进入上一级结构。所以,你如果在
for (i=0; i<10; i++) { ...}
的 for 循环的括号里向右移动,到达右边括号时,就会被提示到达边界。
注意,文档中的注释在这两个操作中会被跳过,这是非常方便的。
5. 进入和退出子结构。
在LISP中,S表达式是嵌套的括号,进入子结构就是进入到这一级 (...) 里面。而在 C 语言中,进入子结构就是进入 (...), {...}, [...] 的里面。 C-M-d: 进入到下一级结构里。C-M-u: 进入到上一级结构里。
C-M-a, C-M-e, C-M-f, C-M-b, C-M-d, C-M-u 这几个命令组合起来可以迅速的在程序里移动。往往手可以按住 C-M 不放,所以还是很顺手的。
6. 段落:
7. M-} (forward-paragraph)
8. M-{ (backward-paragraph)
段落在不同的mode有不同的含义,它的含义是由 paragraph-start 变量决定的。这个正则表达式可以告诉 Emacs 那些符号出现被认为是一个段落开始了。
9. 句子:
10. M-e (backward-sentence)
11. M-a (forward-sentence)
句子在不同的模式有不同的含义。比如在 c-mode, “句子”成为了“语句”的代名词,所以 M-a 和 M-e 可以以语句为单位移动。
句子的含义是由 sentence-end 变量决定的。这个正则表达式可以告诉 Emacs 那些符号出现被认为是一个句子结束了……比如我的 sentence-end 是这样设置的:
(setq sentence-end "\\([。!?]\\|……\\|[.?!][]\"')}]*\\($\\|[ \t]\\)\\)[ \t\n]*")
这样中文的句子就可以被正确识别了。
1. 跳过同一级 tag。
2. C-c C-f 向前跳过同一级 tag (sgml-skip-tag-forward)
3. C-c C-b 向后跳过同一级 tag (sgml-skip-tag-backward)
o C-c C-u 到最近的上一级 \begin{...} 处。
o C-c } 到最近的上一级匹配 {...} 处。
Emacs Fill 详解
Emacs 具有非常智能的文本编辑能力。它可以自动对文字断行,并且在断开的行首都加入一些 prefix(前缀)。
你编辑 C 程序多行注释的时候,你想要编辑器能够自动缩进到合适的位置并且插入一个 "*",就像这样?
/* seed the random number generator
* first try the random file /dev/random
* if there isn't such a file in the system
* use current time to seed the RNG.
*/
在你写新闻组的文章的时候,你又想让编辑器使你的文档出现这样漂亮的缩进:
1. I seed the random number generator first try the random file
/dev/random if there isn't such a file in the system use current
time to seed the RNG.
2. I need more powerful randomized binary search tree algorithm to
store my wavefront elements.
这些 * 和 行首留出的空白就叫做 prefix。每当使用 fill-paragraph 等操作或者启动了 auto-fill-mode 的时候,文字在断行时,Emacs 可能会在断开的每行前面加入 prefix(前缀)。这大大方便了编辑类似程序注释这一类文字。
设置 fill-column
fill-column就是说到多少列的时候断行。你可以使用
C-u 70 C-x f
这样的命令把 fill-column 设置为 70. 也可以把光标移动到你想要断行的位置,然后按
C-u C-x f
断开的行可能会被自动加上一个前缀(prefix)。设置prefix的方式主要有两种,手动设置和 adaptive prefix 自动设置。
手动设置 prefix
如果把光标放在段落首后面一个位置,使用
C-x . (set-fill-prefix)
就可以把段落头到光标处的那段字符作为 prefix.
Adaptive Filling
但是没有手动设置 prefix 的时候,Emacs 也可以自动识别段落首的一些字符作为 prefix。这就叫做 Adaptive Filling。
提取候选前缀
Emacs 使用变量 adaptive-fill-regexp 来提取前缀。这个变量是一个正则表达式。它会把fill区域开头的能够匹配的部分作为候选的前缀。很多 major mode 会自动帮你设置好这个变量,所以你通常不用操心。
但是某些时候,你可能希望能够自己操纵这一切。我们下面就来看一个具体的例子。假设如果你要达到这种效果,在同一个文本文件里:
2. *** Section "Files". The location
3. of the RGB database. Note, this
4. is the name of the file minus
5. the extension (like ".txt" or
6. ".db").
8. * There is normally no need to
9. * change the default. Multiple
10. * FontPath entries are allowed
11. * (they are concatenated together)
12. * By default, Red Hat 6.0 and later
13. * now use a font server independent
14. * of the X server to render fonts.
16. 1. I seed the random number generator first try the random file
17. /dev/random if there isn't such a file in the system use current
18. time to seed the RNG.
19.
20. 2. I need more powerful randomized binary search tree algorithm to
21. store my wavefront elements.
这些 "*** ", "* ", "1. ", "2. ", " " 就叫做前缀。为了识别这些前缀,我们把 adaptive-fill-regexp 设置为:
(setq adaptive-fill-regexp "[ \t]+\\|[ \t]*\\([0-9]+\\.\\|\\*+\\)[ \t]*")
这表示前缀可以全是空白字符。或者开头可以有一些空白,接着数字加点或者一个以上的 *,接着一些空白。那么 Emacs 发现开头有这样的字样时,就会把这个字符串作为一个“候选前缀”。
候选前缀的选择
我们已经轻松提取了可能作为前缀的部分,但是一个候选前缀是否被使用,还有很多因素。Emacs 的策略是非常聪明的。我们下面来看看 Emacs 是怎样为用户着想的。
首先,我们经常有这样一种想法:如果我两行开头都有符合候选前缀条件的符号,编辑器应该把第二行的那个候选作为前缀。如果我们输入一些文字:
1. I seed the random number generator
first try the random file
/dev/random if there isn't such a file in the system use current time to seed the RNG.
我们第一行输入了一个前缀 "1. ",第二行我们故意退了几格,使得 "1. " 这种数字标号突出在段落之外。但是其它的文字我们可以先不用管。那么第一行找到的候选前缀就是 "1. "(1,一个点,一个空格),第二行的候选前缀是" "(3个空格)。
写完一段时,我们按 M-q,这段话就自动采用了第二行的前缀(3个空格)作为 prefix。变成这个样子:
1. I seed the random number generator first try the random file
/dev/random if there isn't such a file in the system use current
time to seed the RNG.
如果我们后来不满意。想把第二行开始的那些行多缩进一些,而且把 fill-column 减小一些。我们可以设置 fill-column,然后在第二行开头再加一些空格,按 M-q。就成了这样:
1. I seed the random number generator
first try the random file
/dev/random if there isn't such
a file in the system use
current time to seed the RNG.
想一下你如果不用 Emacs,如何把上面那段文字变成现在这样!
那么如果我们只输入了一行字就要求把这行的前缀作为所有断开的行的前缀呢?比如,我们输入一行,开头以 * 开始。我们希望它在fill的时候断开的行都以 * 开头。
这是通过设置 adaptive-fill-first-line-regexp 这个变量实现的。这个变量是一个正则表达式。如果它能够匹配我们用 adaptive-fill-regexp 提取出来的前缀,那么这个前缀就被采用。
(setq adaptive-fill-first-line-regexp "^\\* *$")
注意我们没有使用简单的 "\\* *",而是使用了行首和行尾的匹配符号,因为我们只希望 "* " 这样的符号作为单行重复前缀,出现在每行的开头,而不希望 "***" 成为每行的开头。 adaptive-fill-regexp 提取出来的候选前缀被作为了 adaptive-fill-first-line-regexp 的输入行,它有行首和行尾。
我们这是在告诉 Emacs,单行文字如果由一个 * 开头,那么断行后每一行都以 * 作为 prefix。
如果我们输入一行(麻烦你拖动一下:P) :
* There is normally no need to change the default. Multiple FontPath entries are allowed (they are concatenated together) By default, Red Hat 6.0 and later now use a font server independent of the X server to render fonts.
按 M-q,它就变成了:
* There is normally no need to change the default. Multiple FontPath
* entries are allowed (they are concatenated together) By default, Red
* Hat 6.0 and later now use a font server independent of the X server
* to render fonts.
如果adaptive-fill-first-line-regexp 不能匹配从单行取出的前缀,Emacs 会把这个前缀转成同样长度的空格作为前缀,这正是我们想要的结果。比如我们输入:
*** Section "Files". The location of the RGB database. Note, this is the name of the file minus the extension (like ".txt" or ".db").
由于 "^\\* *$" 不能匹配提取出来的候选前缀 "*** ",所以 Emacs 把跟它同样长度的4个空格作为了前缀。这样 fill 之后变成了:
*** Section "Files". The location of the RGB database. Note, this is
the name of the file minus the extension (like ".txt" or ".db").
这正是我们希望的样子。
前面两种情况有一个前提条件,就是候选前缀不能是用来决定段落开头的字符,否则不采用。这很好理解:如果采用这个前缀,我们自动断行的时候插入的字符会把一段话分成好几段话,那么文档的逻辑结构就被破坏了,这是不合理的。
另外,编辑程序的时候,前缀的选择还跟当前的注释符号有关。这个问题超出了本文的范围。
总结
这个规则看起来挺复杂,不过我们可以用算法描述的方式简单的描述出来:
1. 使用 adaptive-fill-regexp 把每行开头部分能够匹配的字符提取出来,作
为“候选前缀”。
2. 如果文字有两行以上,把第二行的候选前缀插入到断开的所有行开头。
3. 如果文字只有一行,看看 adaptive-fill-first-line-regexp 能不能匹配这
行的候选前缀。如果能匹配,使用这个前缀。否则,把这个前缀转成同样长
度的空格,把这些空格作为前缀。
Emacs Outline Mode 示例
Outline mode 是 Emacs 的一个强有力的模式。它可以使你轻松的操纵结构化的文档。它可以让你只显示文档的某一个分支,只显示主干,只显示一个子树。
下面就是一个 LaTeX 文档的各种 outline 操作的结果示范。由于 outline-minor-mode 的键绑定前缀 C-c @ 过于复杂,大部分经常使用 outline 的人想把它设置为另一个键,所以以后我在叙述时直接称呼函数名字和简化前缀的键绑定。具体的键绑定请用 C-h w 查询。
更改前缀可以在启动 outline-minor-mode 之前,用改变 outline-minor-mode-prefix 变量的办法一次完成。比如:
(setq outline-minor-mode-prefix [(control o)])
就可以把前缀改成 C-o. 以后我们实例中的键绑定都使用 C-o.