一個簡略的介紹,講述一下我已經(jīng)知道的 Emacs 的功能。
這里可以找到很多有關(guān) Emacs 的資料。包括入門書籍,網(wǎng)址等。
我搜集到的一些很好的 emacs 擴展。它們使得 Emacs 成為一個多 才多藝的編輯器。
這里簡要記錄一些 Emacs 的使用技巧。便于查詢。
很多人綁定一些比較特殊的鍵的時候,都搞不清楚在
(global-set-key ... ‘my-funtion)
里寫些什么。特別是在 xterm 里的時候就更不知所措了。其實有一個萬無一失的辦法保證你一定寫對。這個辦法就是:
好了,你現(xiàn)在就能在 minibuffer 里看到你應(yīng)該寫在 .emacs 的東西了。
如果你在第1步的時候發(fā)現(xiàn) Emacs 根本對你的按鍵沒有反應(yīng),那么應(yīng)該懷疑是你的窗口管理器攔截了這個按鍵。比如,我的 FVWM 設(shè)置把 C-f3 設(shè)定成了打開一個 FvwmCommand, 所以 Emacs 接收不到這個按鍵。如果我要綁定一個函數(shù)到 C-f3, 我必須讓 FVWM 放過 C-f3。
其實上面的辦法只能讓你綁定一個已有的前綴。你有可能想綁定一個 save-buffer 到 "C-c C-w C-b a"。上面的辦法就不靈了。我們必須使用另外的辦法:
(global-set-key (kbd "C-c C-w C-b a") ‘save-buffer)
上面的那個 "C-c C-w C-b a" 是自動把 "C-c C-w", "C-c C-w C-b" 都定義成了一個 prefix-command. 你可以這樣看到它們:
現(xiàn)在你清楚的看到了 "C-c C-w", "C-c C-w C-b" 都是 prefix command 吧?
(global-set-key (kbd "C-z C-c C-w b") ‘find-file)
出現(xiàn)錯誤:(error "Key sequence C-z C-c C-w b uses invalid prefix characters")
所以你必須事先把第一個鍵設(shè)定為 prefix:
(define-prefix-command ‘ctl-z-map)(global-set-key (kbd "C-z") ‘ctl-z-map)
然后再用
(global-set-key (kbd "C-z C-c C-w b") ‘find-file)
就行了。"C-z C-c" 和 "C-z C-c C-w" 都會自動被定義為 prefix command.
現(xiàn)在我舉一個例子來說明 prefix command 是如何工作的。我們可以把中文的 存盤 兩個字綁定到save-buffer. 這樣你用中文輸入法敲入“存盤”兩個字時,就可以把當前 buffer 保存起來。
(define-prefix-command ‘存-map)(global-set-key (kbd "存") ‘存-map)(define-key 存-map (kbd "盤") ‘save-buffer)
有趣吧?你可以猜到這里面是怎么回事吧?太簡單了是不是?當你輸入“存”的時候,看到 minibuffer 是這樣:
這是因為我們把“存”這個字綁定到了 存-map 這個 prefix-command. 當讀到“存”的時候,Emacs 就會等待下一條命令,這個命令是定義在 存-map 這個 map 里的。它讀到“盤”,就會執(zhí)行 save-buffer 了。
不過注意,你真的要在文檔里輸入“存盤”兩個字就得先打 C-q 了。剛才我就打了好多次 C-q,真累啊。還是用一些不常用的詞組比較好,或者加一個 ctrl 什么的前綴,就像這個,"C-z 存盤"。
(define-prefix-command ‘ctl-z-map)(global-set-key (kbd "C-z") ‘ctl-z-map)(define-key ctl-z-map (kbd "存盤") ‘save-buffer)
看我們更 bt 一點:
(define-prefix-command ‘ctl-z-map)(global-set-key (kbd "C-z") ‘ctl-z-map)(define-key ctl-z-map (kbd "給我存盤啦!") ‘save-buffer)
嗨喲!yes sir!!
define-key 會自動建立很多 prefix command. 不過自己顯式用 define-prefix-command 定義前綴命令有一個好處,就是你可以在你的 prefix 里再方便的定義更多的命令,而不用把整個前綴都寫一遍。
(define-prefix-command ‘ctl-z-map)(define-prefix-command ‘存-map)(define-prefix-command ‘盤-map)(global-set-key (kbd "C-z") ‘ctl-z-map)(define-key ctl-z-map (kbd "存") ‘存-map)(define-key 存-map (kbd "盤") ‘盤-map)(define-key 盤-map (kbd "!") ‘save-buffer)(define-key 盤-map (kbd "到") ‘write-file)(define-key 盤-map (kbd "退出") (lambda () (interactive) (save-buffer) (kill-emacs)))
這樣,到了“C-z 存盤-” 這個時候,我們定義了3個分支:
自定義 prefix command 的另外一個更大的好處就是:你可以修改最上層對 prefix command 的綁定,從而修改許多鍵的綁定。比如,我們可以把 “存盤” 輕而易舉的改成 “保存”:
(define-key ctl-z-map (kbd "保存") ‘盤-map)
這樣一來, “C-z 保存!” ,“C-z 保存到” , “C-z 保存退出” 就分別有了 “C-z 存盤!” , “C-z 存盤到” 和 “C-z 存盤退出” 的含義了。
我覺得這里的設(shè)置對我來說比缺省的設(shè)置方便。
(setq visible-bell t)
關(guān)閉煩人的出錯時的提示聲。
(setq inhibit-startup-message t)
關(guān)閉起動時的那個“開機畫面”。
(setq column-number-mode t)
顯示列號。
(setq mouse-yank-at-point t)
不要在鼠標點擊的那個地方插入剪貼板內(nèi)容。我不喜歡那樣,經(jīng)常把我的文檔搞的一團糟。我覺得先用光標定位,然后鼠標中鍵點擊要好的多。不管你的光標在文檔的那個位置,或是在 minibuffer,鼠標中鍵一點擊,X selection 的內(nèi)容就被插入到那個位置。
(setq kill-ring-max 200)
用一個很大的 kill ring. 這樣防止我不小心刪掉重要的東西。我很努莽的,你知道 :P
(setq default-fill-column 60)
把 fill-column 設(shè)為 60. 這樣的文字更好讀。
(setq-default indent-tabs-mode nil)(setq default-tab-width 8)(setq tab-stop-list ())(loop for x downfrom 40 to 1 do (setq tab-stop-list (cons (* x 4) tab-stop-list)))
不用 TAB 字符來indent, 這會引起很多奇怪的錯誤。編輯 Makefile 的時候也不用擔心,因為 makefile-mode 會把 TAB 鍵設(shè)置成真正的 TAB 字符,并且加亮顯示的。
(setq sentence-end "\\([。?。縘\\|……\\|[.?!][]\"‘)}]*\\($\\|[ \t]\\)\\)[ \t\n]*")(setq sentence-end-double-space nil)
設(shè)置 sentence-end 可以識別中文標點。不用在 fill 時在句號后插入兩個空格。
(setq enable-recursive-minibuffers t)
可以遞歸的使用 minibuffer。我經(jīng)常需要這么做。
(setq scroll-margin 3 scroll-conservatively 10000)
防止頁面滾動時跳動, scroll-margin 3 可以在靠近屏幕邊沿3行時就開始滾動,可以很好的看到上下文。
(setq default-major-mode ‘text-mode)
把缺省的 major mode 設(shè)置為 text-mode, 而不是幾乎什么功能也沒有的 fundamental-mode.
(show-paren-mode t)(setq show-paren-style ‘parentheses)
括號匹配時顯示另外一邊的括號,而不是煩人的跳到另一個括號。
(mouse-avoidance-mode ‘a(chǎn)nimate)
光標靠近鼠標指針時,讓鼠標指針自動讓開,別擋住視線。
(setq frame-title-format "emacs@%b")
在標題欄顯示buffer的名字,而不是 emacs@wangyin.com 這樣沒用的提示。
(auto-image-file-mode)
讓 Emacs 可以直接打開和顯示圖片。
(global-font-lock-mode t)
進行語法加亮。
(put ‘set-goal-column ‘disabled nil)(put ‘narrow-to-region ‘disabled nil)(put ‘upcase-region ‘disabled nil)(put ‘downcase-region ‘disabled nil)(put ‘LaTeX-hide-environment ‘disabled nil)
把這些缺省禁用的功能打開。
(setq version-control t)(setq kept-new-versions 3)(setq delete-old-versions t)(setq kept-old-versions 2)(setq dired-kept-versions 1)
設(shè)置一下備份時的版本控制,這樣更加安全。
(mapcar (function (lambda (setting) (setq auto-mode-alist (cons setting auto-mode-alist)))) ‘(("\\.xml$". sgml-mode) ("\\\.bash" . sh-mode) ("\\.rdf$". sgml-mode) ("\\.session" . emacs-lisp-mode) ("\\.l$" . c-mode) ("\\.css$" . css-mode) ("\\.cfm$" . html-mode) ("gnus" . emacs-lisp-mode) ("\\.idl$" . idl-mode)))
一個簡單的辦法設(shè)置 auto-mode-alist, 免得寫很多 add-to-list.
(setq user-full-name "Wang Yin")(setq user-mail-address "wang-y01@mails.tsinghua.edu.cn")
設(shè)置有用的個人信息。這在很多地方有用。
(setq dired-recursive-copies ‘top)(setq dired-recursive-deletes ‘top)
讓 dired 可以遞歸的拷貝和刪除目錄。
文檔一般都有各種結(jié)構(gòu),比如LISP里有S表達式,C語言里的函數(shù),LaTeX 里的 \begin{...}...\end{...} ... 如果我們能夠已文檔的語法單位來移動,就會使操作非常高效。
C-M-a 到 defun 頭C-M-e 到 defun 尾這樣,我們在C語言程序里可以一個函數(shù)一個函數(shù)的跳過。也可以從一個函數(shù)中間一下跳到函數(shù)開頭或末尾。
所以在 Emacs 里,尋找匹配的括號可以在括號處使用 C-M-f 和 C-M-b.
C-M-f 到下一個同級語法結(jié)構(gòu)C-M-b 到上一個同級語法結(jié)構(gòu)
注意,這種移動不能越過語法結(jié)構(gòu)的邊界而進入上一級結(jié)構(gòu)。所以,你如果在
for (i=0; i<10; i++) { ...}的 for 循環(huán)的括號里向右移動,到達右邊括號時,就會被提示到達邊界。
注意,文檔中的注釋在這兩個操作中會被跳過,這是非常方便的。
在LISP中,S表達式是嵌套的括號,進入子結(jié)構(gòu)就是進入到這一級 (...) 里面。而在 C 語言中,進入子結(jié)構(gòu)就是進入 (...), {...}, [...] 的里面。 C-M-d: 進入到下一級結(jié)構(gòu)里。C-M-u: 進入到上一級結(jié)構(gòu)里。
C-M-a, C-M-e, C-M-f, C-M-b, C-M-d, C-M-u 這幾個命令組合起來可以迅速的在程序里移動。往往手可以按住 C-M 不放,所以還是很順手的。
M-} (forward-paragraph)M-{ (backward-paragraph)段落在不同的mode有不同的含義,它的含義是由 paragraph-start 變量決定的。這個正則表達式可以告訴 Emacs 那些符號出現(xiàn)被認為是一個段落開始了。
M-e (backward-sentence)M-a (forward-sentence)
句子在不同的模式有不同的含義。比如在 c-mode, “句子”成為了“語句”的代名詞,所以 M-a 和 M-e 可以以語句為單位移動。
句子的含義是由 sentence-end 變量決定的。這個正則表達式可以告訴 Emacs 那些符號出現(xiàn)被認為是一個句子結(jié)束了……比如我的 sentence-end 是這樣設(shè)置的:
(setq sentence-end "\\([。?。縘\\|……\\|[.?!][]\"‘)}]*\\($\\|[ \t]\\)\\)[ \t\n]*")這樣中文的句子就可以被正確識別了。
C-c C-f 向前跳過同一級 tag (sgml-skip-tag-forward)C-c C-b 向后跳過同一級 tag (sgml-skip-tag-backward)
C-c C-u 到最近的上一級 \begin{...} 處。
C-c } 到最近的上一級匹配 {...} 處。
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. */
在你寫新聞組的文章的時候,你又想讓編輯器使你的文檔出現(xiàn)這樣漂亮的縮進:
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就是說到多少列的時候斷行。你可以使用
C-u 70 C-x f
這樣的命令把 fill-column 設(shè)置為 70. 也可以把光標移動到你想要斷行的位置,然后按
C-u C-x f
斷開的行可能會被自動加上一個前綴(prefix)。設(shè)置prefix的方式主要有兩種,手動設(shè)置和 adaptive prefix 自動設(shè)置。
如果把光標放在段落首后面一個位置,使用
C-x . (set-fill-prefix)
就可以把段落頭到光標處的那段字符作為 prefix.
但是沒有手動設(shè)置 prefix 的時候,Emacs 也可以自動識別段落首的一些字符作為 prefix。這就叫做 Adaptive Filling。
Emacs 使用變量 adaptive-fill-regexp 來提取前綴。這個變量是一個正則表達式。它會把fill區(qū)域開頭的能夠匹配的部分作為候選的前綴。很多 major mode 會自動幫你設(shè)置好這個變量,所以你通常不用操心。
但是某些時候,你可能希望能夠自己操縱這一切。我們下面就來看一個具體的例子。假設(shè)如果你要達到這種效果,在同一個文本文件里:
*** Section "Files". The location of the RGB database. Note, this is the name of the file minus the extension (like ".txt" or ".db").
* 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.
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.
這些 "*** ", "* ", "1. ", "2. ", " " 就叫做前綴。為了識別這些前綴,我們把 adaptive-fill-regexp 設(shè)置為:
(setq adaptive-fill-regexp "[ \t]+\\|[ \t]*\\([0-9]+\\.\\|\\*+\\)[ \t]*")
這表示前綴可以全是空白字符?;蛘唛_頭可以有一些空白,接著數(shù)字加點或者一個以上的 *,接著一些空白。那么 Emacs 發(fā)現(xiàn)開頭有這樣的字樣時,就會把這個字符串作為一個“候選前綴”。
我們已經(jīng)輕松提取了可能作為前綴的部分,但是一個候選前綴是否被使用,還有很多因素。Emacs 的策略是非常聰明的。我們下面來看看 Emacs 是怎樣為用戶著想的。
首先,我們經(jīng)常有這樣一種想法:如果我兩行開頭都有符合候選前綴條件的符號,編輯器應(yīng)該把第二行的那個候選作為前綴。如果我們輸入一些文字:
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. " 這種數(shù)字標號突出在段落之外。但是其它的文字我們可以先不用管。那么第一行找到的候選前綴就是 "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 減小一些。我們可以設(shè)置 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,如何把上面那段文字變成現(xiàn)在這樣!
那么如果我們只輸入了一行字就要求把這行的前綴作為所有斷開的行的前綴呢?比如,我們輸入一行,開頭以 * 開始。我們希望它在fill的時候斷開的行都以 * 開頭。
這是通過設(shè)置 adaptive-fill-first-line-regexp 這個變量實現(xiàn)的。這個變量是一個正則表達式。如果它能夠匹配我們用 adaptive-fill-regexp 提取出來的前綴,那么這個前綴就被采用。
(setq adaptive-fill-first-line-regexp "^\\* *$")
注意我們沒有使用簡單的 "\\* *",而是使用了行首和行尾的匹配符號,因為我們只希望 "* " 這樣的符號作為單行重復前綴,出現(xiàn)在每行的開頭,而不希望 "***" 成為每行的開頭。 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 會把這個前綴轉(zhuǎn)成同樣長度的空格作為前綴,這正是我們想要的結(jié)果。比如我們輸入:
*** 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").這正是我們希望的樣子。
前面兩種情況有一個前提條件,就是候選前綴不能是用來決定段落開頭的字符,否則不采用。這很好理解:如果采用這個前綴,我們自動斷行的時候插入的字符會把一段話分成好幾段話,那么文檔的邏輯結(jié)構(gòu)就被破壞了,這是不合理的。
另外,編輯程序的時候,前綴的選擇還跟當前的注釋符號有關(guān)。這個問題超出了本文的范圍。
這個規(guī)則看起來挺復雜,不過我們可以用算法描述的方式簡單的描述出來:
1. 使用 adaptive-fill-regexp 把每行開頭部分能夠匹配的字符提取出來,作 為“候選前綴”。2. 如果文字有兩行以上,把第二行的候選前綴插入到斷開的所有行開頭。3. 如果文字只有一行,看看 adaptive-fill-first-line-regexp 能不能匹配這 行的候選前綴。如果能匹配,使用這個前綴。否則,把這個前綴轉(zhuǎn)成同樣長 度的空格,把這些空格作為前綴。
Outline mode
是 Emacs 的一個強有力的模式。它可以使你輕松的操縱結(jié)構(gòu)化的文檔。它可以讓你只顯示文檔的某一個分支,只顯示主干,只顯示一個子樹。
下面就是一個 LaTeX 文檔的各種 outline 操作的結(jié)果示范。由于 outline-minor-mode 的鍵綁定前綴 C-c @ 過于復雜,大部分經(jīng)常使用 outline 的人想把它設(shè)置為另一個鍵,所以以后我在敘述時直接稱呼函數(shù)名字和簡化前綴的鍵綁定。具體的鍵綁定請用 C-h w 查詢。
更改前綴可以在啟動 outline-minor-mode 之前,用改變 outline-minor-mode-prefix 變量的辦法一次完成。比如:
(setq outline-minor-mode-prefix [(control o)])
就可以把前綴改成 C-o. 以后我們實例中的鍵綁定都使用 C-o.
首先,給大家一個 outline 的總體印象。我們使用 outline 來看看本文的主要內(nèi)容 :)
這是一個非常簡單的 LaTeX 文檔: outline.tex
我們來把文檔的各部分術(shù)語解釋一下。
M-x outline-minor-mode 就可以啟動 Outline。還有一個 outline-mode 是一個 major mode,一般都不用它。
光標在任何位置,只要執(zhí)行這些操作,文檔的顯示就會變化成需要的樣子。
這個操作如果不帶參數(shù),隱藏所有文檔子結(jié)構(gòu),只剩最上層。
這是參數(shù)為4的操作,顯示至文檔第4層子結(jié)構(gòu)。
文檔的所有 Entry 都被隱藏。只顯示主干。
所有文檔部分展開時,光標移動到 Chapter 1,執(zhí)行 hide-subtree。整個 Chapter 1 的子樹被折疊起來。
所有文檔部分展開時,光標移動到 Chapter 1,執(zhí)行 hide-other。除了 Chapter 1,其它子樹全部被折疊起來。這個操作正好與 hide-subtree 互補。
所有文檔部分展開時,光標移動到 Chapter 1,執(zhí)行 hide-leaves。所有 Chapter 1 子樹下的所有級別的 entry 被隱藏。也就是說,Chapter 1 下,只顯示 branch.
所有文檔部分展開時,光標移動到 Chapter 1,執(zhí)行 hide-entry。Chapter 1 的 Entry 被隱藏,但是所有子樹都不動。
顯示所有文檔。結(jié)果就是原文檔。
為了演示,我們從全部隱藏的情況開始:
show-children 只顯示直接的下一代子樹,而不顯示間接的下一代。這里, \begin{document} 的直接的下一代就是 \chapter{...}。
把光標移動到 Chapter 1,執(zhí)行 show-entry。Chapter 1 的 Entry 被顯示,但是所有子結(jié)構(gòu)還是保持隱藏。
把光標移動到 Chapter 1,執(zhí)行 show-branches。Chapter 1 這棵子樹之下的各級“樹干”被顯示,但是各級 entry 還是保持隱藏。Chapter 1 自己的 entry,由于我們上一步已經(jīng)顯示,所以保持不變。
把光標移動到 Chapter 1,執(zhí)行 show-subtree。Chapter 1 及其所有子結(jié)構(gòu)全部被擴展。
在 outline 模式下,有幾種特殊方便的移動方式。