使用 Emacs 作爲開發環境
1 Emacs
很不幸,UNIX ® 系統不像其他的系統那樣帶有一種“你要的全有,不要的更多”的,包含所有的,巨大的程序開發環境。 [1] 但是,你可以搭建一個自己的開發環境。可能不會很漂亮,也不會非常集成化。但是你可以按自己的需求來搭建。而且是免費的。你將擁有所有的源碼。
問題的答案就是 Emacs。如今有很多人厭惡它,也有很多喜歡它。如果你是前者之一,恐怕這一章不會引起你的興趣。而且,你需要一定量的內存來運行 Emacs──文字界面我推薦 8MB,而在 X 下最少需要 16MB 來獲得合理的性能。
Emacs 基本上是一個高度可配置的編輯器──實際上,Emacs 更像一個操作系統而不像一個編輯器!很多開發人員和系統管理員把所有的時間都花在 Emacs 裏面,只在退出登陸的時候才退出這個編輯器。
要在這裏概括所有 Emacs 能做的事情是不可能的,但是這裏列出了一些開發人員可能感興趣的特性:
-
非常強大的編輯器,允許對字串和正則表達式(類型)進行搜索和替代。跳至塊結構的開始/末尾,等等。
-
下拉菜單和在線幫助。
-
語言相關的語法高亮顯示和縮進。
-
完全可配置。
-
你可以在 Emacs 中編譯和調試程序。
-
出現編譯錯誤以後,你可以直接跳至出問題的那一行代碼。
-
比較友好的 info 的前端,可以閱讀 GNU 超文本文 檔。當然包括 Emacs 自己的文檔。
-
友好的 gdb 的前端,允許你在追蹤程序的時候查看 源代碼。
-
你可以在編譯程序的同時查看 Usenet 新聞和閱讀郵件。
毫無疑問還有很多被我忽略的。
在 FreeBSD 上可以用 editors/emacs port 來安裝 Emacs。
一旦安裝好了,就可以運行 Emacs,然後輸入 C-h t 閱讀 Emacs 教程──意思就是說按住 control ,再按 h ,鬆開 control ,然後再按 t 。(或者,你可以使用鼠 標從 Help 菜單重選擇 Emacs Tutorial )。
儘管 Emacs 有菜單,最好還是學習一下鍵組合。因爲在你編輯的時候,連續地按下一系列按鍵,比找到鼠標然後點擊正確的地方要快得多。而且,當你和一個老 Emacs 用戶交流的時候,你經常會碰到下列的表達 “M-x replace-s RET foo RET bar RET ”,因此知道這些東西會很有用。而且在任 何情況下,Emacs 的菜單裏永遠放不下所有它實際上擁有的有用的功能。
幸運的是,很容易學習鍵組合。因爲菜單每個項目的後面都標示了對應的鍵組合。我的建議就是,首先使用菜單項,比如,打開一個文件,直到你明白了其中的奧妙,並且可以自信的使用這個菜單項,再嘗試使用 C-x C-f。當你一點困難也沒有的時候,就可以轉到下一個菜單項繼續練習。
如果記不住一個特殊的鍵組合到底能做什麼,可以從 Help 菜單中選擇 Describe Key ,然後輸入這個鍵組合──Emacs 會告訴你它到底能幹什麼。你也可以點擊 Command Apropos 來尋找包含一個特定詞的命令, 後面緊跟的就是鍵組合。
另外,剛纔那個表達式的意思就是按住 Meta 鍵,按下 x 鍵,鬆開 Meta 鍵,輸入 replace-s (replace-string 的簡寫 ──Emacs 另一個特性就是命令的縮寫),按下 return 鍵,輸 入 foo (你要替換的字串),輸入 bar (你要用來替換 foo 的字串) 然後再次按下 return 鍵。 Emacs 就會按你的要求進行搜索和替換操作。
你一定在疑惑 Meta 鍵是個什麼鍵。這是一個很多 UNIX 工作站都有的特殊的鍵。很不幸,PC沒有這樣一個鍵。通常在 PC 上這個鍵是 alt 鍵(如果你運氣不好,這個鍵在你的 PC 上會是 escape 鍵)。
哦,要退出 Emacs,鍵入 C-x C-c (意思就是按住 control 鍵,按下 x ,按下 c ,再鬆開 control 鍵)。如果你還有已經打開的未保存的文件,Emacs 會問你是否要保存文件。(不要理會文檔中說的退出 Emacs 的常用方法 C-z ──這個鍵組合會把 Emacs 放到後臺,而且這個方法只在沒有虛擬控制檯的系統上有用)。
2 配置 Emacs
Emacs 能做很多有用的事情;一些是內置的,另外一些需要我們進行配置。
Emacs 沒有用一種私有的宏語言來配置自身,而是使用了某種特別適應編輯器 的 Lisp 版本,叫做 Emacs Lisp。如果你要繼續讀下去並且想學習一點 Common Lisp,學習使用 Emacs Lisp 是很有用的。Emacs Lisp 有很多 Common Lisp 的特性, 雖然前者相當小 (因此更容易掌握)。
學習 Emacs Lisp 最好的方法就是下載 Emacs Tutorial 。
但是,要配置 Emacs 並不需要任何實際的 Lisp 知識,因爲我已經列出了一 個 .emacs 例子,足夠讓你順利的開始工作。只要把這個文件複製到你的家目錄,如果 Emacs 已經在運行,就重新起動;Emacs 會從這個文件中讀取命令,然後(希望)能給你一個有用的基本設置。
3 一個 .emacs 配置文件的例子
不幸的是,要詳細解釋的話話就長了;但是還是有一兩點值得注意。
-
以 ; 開頭的是註釋,會被 Emacs 忽略掉。
-
第一行裏面的 -*- Emacs-Lisp -*- 能 讓我們在 Emacs 裏面編輯這個 .emacs ,並且打開所 有 Emacs Lisp 的編輯特性。Emacs 一般會嘗試根據文件名來猜測,而且很有可 能猜錯。
-
在某些模式下,tab 鍵被綁定到一個縮進函數上。因 此按下 tab 鍵後,它能縮進一行代碼。如果你想把 tab 當作一個字符插入到你編輯的東西里面,需要在按下 tab 鍵的時同時按住 control 鍵。
-
這個文件通過識別文件名後綴來支持 C,C++,Perl,Lisp 和 Scheme 的語法高亮。
-
Emacs 已經有一個預先定義的函數叫
next-error
。在一個編譯錯誤輸出窗口,按下 M-n 能讓從一個編譯錯誤移動到另一個;我們還定義了一個類似的函數,previous-error
,這個函數在你按下 M-p 後,能讓你回到上一個編譯錯誤。其中最好的特性就是按 下 C-c C-c 後,能根據錯誤打開相應的文件並且跳到相應 的那行代碼。 -
我們打開了 Emacs 作爲 服務端運行的特性,這樣當你在 Emacs 外做一些事情的時候,又需要編輯一個文件的時候,只需要輸入
% emacsclient filename
然後就可以在 Emacs 編輯那個文件了! [2]
例 2-1. 一個 .emacs 配置文件的例子
;; -*-Emacs-Lisp-*- ;; This file is designed to be re-evaled; use the variable first-time ;; to avoid any problems with this. (defvar first-time t "Flag signifying this is the first time that .emacs has been evaled") ;; Meta (global-set-key "/M- " 'set-mark-command) (global-set-key "/M-/C-h" 'backward-kill-word) (global-set-key "/M-/C-r" 'query-replace) (global-set-key "/M-r" 'replace-string) (global-set-key "/M-g" 'goto-line) (global-set-key "/M-h" 'help-command) ;; Function keys (global-set-key [f1] 'manual-entry) (global-set-key [f2] 'info) (global-set-key [f3] 'repeat-complex-command) (global-set-key [f4] 'advertised-undo) (global-set-key [f5] 'eval-current-buffer) (global-set-key [f6] 'buffer-menu) (global-set-key [f7] 'other-window) (global-set-key [f8] 'find-file) (global-set-key [f9] 'save-buffer) (global-set-key [f10] 'next-error) (global-set-key [f11] 'compile) (global-set-key [f12] 'grep) (global-set-key [C-f1] 'compile) (global-set-key [C-f2] 'grep) (global-set-key [C-f3] 'next-error) (global-set-key [C-f4] 'previous-error) (global-set-key [C-f5] 'display-faces) (global-set-key [C-f8] 'dired) (global-set-key [C-f10] 'kill-compilation) ;; Keypad bindings (global-set-key [up] "/C-p") (global-set-key [down] "/C-n") (global-set-key [left] "/C-b") (global-set-key [right] "/C-f") (global-set-key [home] "/C-a") (global-set-key [end] "/C-e") (global-set-key [prior] "/M-v") (global-set-key [next] "/C-v") (global-set-key [C-up] "/M-/C-b") (global-set-key [C-down] "/M-/C-f") (global-set-key [C-left] "/M-b") (global-set-key [C-right] "/M-f") (global-set-key [C-home] "/M-<") (global-set-key [C-end] "/M->") (global-set-key [C-prior] "/M-<") (global-set-key [C-next] "/M->") ;; Mouse (global-set-key [mouse-3] 'imenu) ;; Misc (global-set-key [C-tab] "/C-q/t") ; Control tab quotes a tab. (setq backup-by-copying-when-mismatch t) ;; Treat 'y' or <CR> as yes, 'n' as no. (fset 'yes-or-no-p 'y-or-n-p) (define-key query-replace-map [return] 'act) (define-key query-replace-map [?/C-m] 'act) ;; Load packages (require 'desktop) (require 'tar-mode) ;; Pretty diff mode (autoload 'ediff-buffers "ediff" "Intelligent Emacs interface to diff" t) (autoload 'ediff-files "ediff" "Intelligent Emacs interface to diff" t) (autoload 'ediff-files-remote "ediff" "Intelligent Emacs interface to diff") (if first-time (setq auto-mode-alist (append '(("//.cpp$" . c++-mode) ("//.hpp$" . c++-mode) ("//.lsp$" . lisp-mode) ("//.scm$" . scheme-mode) ("//.pl$" . perl-mode) ) auto-mode-alist))) ;; Auto font lock mode (defvar font-lock-auto-mode-list (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode) "List of modes to always start in font-lock-mode") (defvar font-lock-mode-keyword-alist '((c++-c-mode . c-font-lock-keywords) (perl-mode . perl-font-lock-keywords)) "Associations between modes and keywords") (defun font-lock-auto-mode-select () "Automatically select font-lock-mode if the current major mode is in font-lock-auto-mode-list" (if (memq major-mode font-lock-auto-mode-list) (progn (font-lock-mode t)) ) ) (global-set-key [M-f1] 'font-lock-fontify-buffer) ;; New dabbrev stuff ;(require 'new-dabbrev) (setq dabbrev-always-check-other-buffers t) (setq dabbrev-abbrev-char-regexp "//sw//|//s_") (add-hook 'emacs-lisp-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) nil) (set (make-local-variable 'dabbrev-case-replace) nil))) (add-hook 'c-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) nil) (set (make-local-variable 'dabbrev-case-replace) nil))) (add-hook 'text-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) t) (set (make-local-variable 'dabbrev-case-replace) t))) ;; C++ and C mode... (defun my-c++-mode-hook () (setq tab-width 4) (define-key c++-mode-map "/C-m" 'reindent-then-newline-and-indent) (define-key c++-mode-map "/C-ce" 'c-comment-edit) (setq c++-auto-hungry-initial-state 'none) (setq c++-delete-function 'backward-delete-char) (setq c++-tab-always-indent t) (setq c-indent-level 4) (setq c-continued-statement-offset 4) (setq c++-empty-arglist-indent 4)) (defun my-c-mode-hook () (setq tab-width 4) (define-key c-mode-map "/C-m" 'reindent-then-newline-and-indent) (define-key c-mode-map "/C-ce" 'c-comment-edit) (setq c-auto-hungry-initial-state 'none) (setq c-delete-function 'backward-delete-char) (setq c-tab-always-indent t) ;; BSD-ish indentation style (setq c-indent-level 4) (setq c-continued-statement-offset 4) (setq c-brace-offset -4) (setq c-argdecl-indent 0) (setq c-label-offset -4)) ;; Perl mode (defun my-perl-mode-hook () (setq tab-width 4) (define-key c++-mode-map "/C-m" 'reindent-then-newline-and-indent) (setq perl-indent-level 4) (setq perl-continued-statement-offset 4)) ;; Scheme mode... (defun my-scheme-mode-hook () (define-key scheme-mode-map "/C-m" 'reindent-then-newline-and-indent)) ;; Emacs-Lisp mode... (defun my-lisp-mode-hook () (define-key lisp-mode-map "/C-m" 'reindent-then-newline-and-indent) (define-key lisp-mode-map "/C-i" 'lisp-indent-line) (define-key lisp-mode-map "/C-j" 'eval-print-last-sexp)) ;; Add all of the hooks... (add-hook 'c++-mode-hook 'my-c++-mode-hook) (add-hook 'c-mode-hook 'my-c-mode-hook) (add-hook 'scheme-mode-hook 'my-scheme-mode-hook) (add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook) (add-hook 'lisp-mode-hook 'my-lisp-mode-hook) (add-hook 'perl-mode-hook 'my-perl-mode-hook) ;; Complement to next-error (defun previous-error (n) "Visit previous compilation error message and corresponding source code." (interactive "p") (next-error (- n))) ;; Misc... (transient-mark-mode 1) (setq mark-even-if-inactive t) (setq visible-bell nil) (setq next-line-add-newlines nil) (setq compile-command "make") (setq suggest-key-bindings nil) (put 'eval-expression 'disabled nil) (put 'narrow-to-region 'disabled nil) (put 'set-goal-column 'disabled nil) (if (>= emacs-major-version 21) (setq show-trailing-whitespace t)) ;; Elisp archive searching (autoload 'format-lisp-code-directory "lispdir" nil t) (autoload 'lisp-dir-apropos "lispdir" nil t) (autoload 'lisp-dir-retrieve "lispdir" nil t) (autoload 'lisp-dir-verify "lispdir" nil t) ;; Font lock mode (defun my-make-face (face color &optional bold) "Create a face from a color and optionally make it bold" (make-face face) (copy-face 'default face) (set-face-foreground face color) (if bold (make-face-bold face)) ) (if (eq window-system 'x) (progn (my-make-face 'blue "blue") (my-make-face 'red "red") (my-make-face 'green "dark green") (setq font-lock-comment-face 'blue) (setq font-lock-string-face 'bold) (setq font-lock-type-face 'bold) (setq font-lock-keyword-face 'bold) (setq font-lock-function-name-face 'red) (setq font-lock-doc-string-face 'green) (add-hook 'find-file-hooks 'font-lock-auto-mode-select) (setq baud-rate 1000000) (global-set-key "/C-cmm" 'menu-bar-mode) (global-set-key "/C-cms" 'scroll-bar-mode) (global-set-key [backspace] 'backward-delete-char) ; (global-set-key [delete] 'delete-char) (standard-display-european t) (load-library "iso-transl"))) ;; X11 or PC using direct screen writes (if window-system (progn ;; (global-set-key [M-f1] 'hilit-repaint-command) ;; (global-set-key [M-f2] [?/C-u M-f1]) (setq hilit-mode-enable-list '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode scheme-mode) hilit-auto-highlight nil hilit-auto-rehighlight 'visible hilit-inhibit-hooks nil hilit-inhibit-rebinding t) (require 'hilit19) (require 'paren)) (setq baud-rate 2400) ; For slow serial connections ) ;; TTY type terminal (if (and (not window-system) (not (equal system-type 'ms-dos))) (progn (if first-time (progn (keyboard-translate ?/C-h ?/C-?) (keyboard-translate ?/C-? ?/C-h))))) ;; Under UNIX (if (not (equal system-type 'ms-dos)) (progn (if first-time (server-start)))) ;; Add any face changes here (add-hook 'term-setup-hook 'my-term-setup-hook) (defun my-term-setup-hook () (if (eq window-system 'pc) (progn ;; (set-face-background 'default "red") ))) ;; Restore the "desktop" - do this as late as possible (if first-time (progn (desktop-load-default) (desktop-read))) ;; Indicate that this file has been read at least once (setq first-time nil) ;; No need to debug anything now (setq debug-on-error nil) ;; All done (message "All done, %s%s" (user-login-name) ".")
4 擴展 Emacs 所支持語言的範圍
現在,如果你只是想用 .emacs 設定好的語言 (C, C++, Perl, Lisp 和 Scheme) 來編程,事情就很好辦。但是,如果突然一個新的語 言,叫 “whizbang”,有很多激動人心的特性,出來了,會發生什麼事情?
第一件要做的事情就是找到是否有任何文件能夠告訴 Emacs 關於這個語言的信息。這種文件通常以 .el 結尾,是 “Emacs Lisp” 的縮寫。例如,如果 whizbang 是 FreeBSD 的一個 port,那麼我們可以用如下命令來定位這些文件
% find /usr/ports/lang/whizbang -name "*.el" -print
然後安裝這些文件到 Emacs 的系統級 Lisp 目錄。在 FreeBSD 2.1.0-Release 裏,這個目錄就是 /usr/local/share/emacs/site-lisp 。
例如,如果剛纔的定位命令的輸出是
/usr/ports/lang/whizbang/work/misc/whizbang.el
我們可以執行
# cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp
下一步,我們需要確定 whizbang 的源文件是以什麼後綴結尾。我們假定這些 源文件都是以 .wiz 結尾。我們需要在 .emacs 加上一條使 Emacs 能夠使用 whizbang.el 中的信息。
在 .emacs
中找到 auto-mode-alist
entry
,爲 whizbang 添加一行,例如:
... ("//.lsp$" . lisp-mode) ("//.wiz$" . whizbang-mode) ("//.scm$" . scheme-mode) ...
意思就是,當你編輯一個以 .wiz
結尾的文件的時候, Emacs 會自動進入 whizbang-mode
。
就在下面,你會發現 font-lock-auto-mode-list
這一條。添加 whizbang-mode
;; Auto font lock mode (defvar font-lock-auto-mode-list (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode) "List of modes to always start in font-lock-mode")
這意味着當你編輯 .wiz
文件的時候,Emacs 會自動 打開 font-lock-mode
(就是語法高亮)。
這就是所有必要的步驟。如果在你打開一個 .wiz
文件的時候,還有需要自動執行的任何其他步驟,你可以添加一個 whizbang-mode hook
(查看
my-scheme-mode-hook
中添加 auto-indent
的步驟作爲例子)。
備註
[1] |
現在在 Ports Collection 中包含了一些強大的,免費的 IDE,比如 KDevelop。 |
[2] |
很多 Emacs 用戶把他們的 EDITOR 環境變量設置爲 emacsclient ,因此每次他們需要編輯一個文件的時候,以上的動作就會被執行。 |