使用 Emacs 作爲開發環境

使用 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 ,因此每次他們需要編輯一個文件的時候,以上的動作就會被執行。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章