如何製作一個 Major Mode

為了寫萌典的 Emacs 版 client 去查了一下 major mode 的製作方式。

然而 Google 下來,找到的很多其實都是舊寫法例如每個 face 除了 defface 外還要另外 defvar 一次,現在根本沒有必要這樣做。,在 Emacs 24 其實可以寫得很簡單。除非你想要支援舊版 Emacs,不然當然是能輕鬆就輕鬆。

注意

本篇介紹的 不是 在寫一個新語言的 major mode,而是寫出類似 dired 的那種 major mode。
如果你是要給一個新的語言寫 major mode 與 syntax highlight 之類的東西,請參考 coldnew 大神的「使用 Generic Mode 輕鬆建立新語言的語法上色」與用 Generic Mode 寫的 coldnew/qml-mode ;或者傳統的寫法 cataska/qml-mode

假設要寫萌典的 client 端:

  1. mode 名叫做 moedict-mode
  2. 檔名叫做 moedict.el
  3. group 名叫做 moedict

Group

首先定義一個 group,名子就叫做 moedict ,把你寫的這個 mode 所有相關的 function, variable 都全部放進去。定義 function, variable 時記得都以 prefix 當開頭。
至於 font-faces 放進一個獨立的 group moedict-faces,然後塞進 group moedictfaces(預設的 faces group 名) 作為 sub-group。

1
2
3
4
5
6
7
8
9
(defgroup moedict nil
"Major mode for looking up Chinese vocabulary via Moedict API."
:prefix "moedict-"
:link '(url-link "http://github.com/kuanyui/moedict.el"))

(defgroup moedict-faces nil
"Faces used in Moedict-mode"
:group 'moedict
:group 'faces)

Mode Hook

再來定義這個 mode 的 hookHook: 在 mode 啟動完後 Emacs 會自動去讀該 mode 的 hook 並執行裡面的內容,這種機制讓使用者可以自行額外自訂這個 mode 變數:

1
2
3
4
(defcustom moedict-mode-hook nil
"Normal hook run when entering moedict-mode."
:type 'hook
:group 'moedict)

Key Map

再來定義這個 mode 所使用的 key-map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defvar moedict-mode-map
(let ((map (make-sparse-keymap)))
;; Element insertion
(define-key map (kbd "q") 'quit-window)
(define-key map (kbd "h") 'describe-mode)
(define-key map (kbd "l") 'moedict-lookup)
(define-key map (kbd "r") 'moedict-lookup-region)
(define-key map (kbd "<tab>") 'moedict-cursor-forward-word)
(define-key map (kbd "<backtab>") 'moedict-cursor-backward-word)
(define-key map (kbd "C-c C-b") 'moedict-history-backward)
(define-key map (kbd "C-c C-f") 'moedict-history-forward)
(define-key map (kbd "C-c D") 'moedict-history-clean)
map)
"Keymap for Moedict major mode.") ;document

基本上照樣照句就對了。

Major Mode

最重要的地方來了,但其實很簡單,Emacs 24 只需要用 define-derived-mode 這個內建 macro 就能一次解決。

1
2
3
(define-derived-mode moedict-mode nil "MoeDict"
"Major mode for looking up Chinese vocabulary via Moedict API." ;major-mode 說明
(set (make-local-variable 'buffer-read-only) t)) ;這裡放啟動 mode 後要改的 local 變數

注意第一和第二個參數分別是是 child 和 parent mode,child 就是指目前的 mode 名稱。指定 parent mode 以繼承 parent mode 的設定,例如 css-mode 的 parent mode 是 fundamental-mode只有純文字、什麼都沒有的 modepython-mode則是屬於 prog-modeprog=programming
第三個參數則是會顯示在 mode-line 上的字串。

Faces

如果你的 mode 需要用到 font face也就是字體大小顏色粗細斜底線等,就需要為不同的 face 分別定義。詳細可以參考敝人曾經寫過的這篇
這裡要注意的是,Emacs 能夠自動根據目前的背景色(深色底/淺色底)來決定該用哪一個 face,所以你可以定得像是下面這樣:

1
2
3
4
5
6
7
(defface moedict-title
'((((class color) (background light))
(:foreground "#ff8700" :bold t :height 1.2))
(((class color) (background dark))
(:foreground "#ffa722" :bold t :height 1.2)))
"Face for title. ex:"
:group 'moedict-faces)

light 表示淺色底所採用的 face,這時文字就建議用深色一點的才看得清楚;dark 則反之。

參考資料