這篇並不是 Emacs Lisp 教學,而是一些瑣碎筆記。當初不知道這種問題該怎麼問人或問誰,所以一開始寫 Emacs Lisp 時覺得很痛苦。
在這裡分享一些能讓你在開發 Emacs Lisp 時更輕鬆方便的小技巧,需要的人許可以參考。
- 在任何 mode 下,
C-x C-e
可以將 游標之前 的 S-expression(就是 Lisp 運算式,常簡寫成 *sexp*)eval (求值) 並在 minibuffer 中顯示結果。- 前面加一個
C-u
prefix 的話會把結果插入目前游標位置。(所以高興的話可以在 Emacs 裡任何地方寫 Lisp 式子來當計算機。) - 如果你在
emacs-lisp-mode
下,C-M-x
(eval-defun
) 則能夠 eval 目前的defun
。 - 因為覺得內建的 eval 快速鍵要拿來作其他用途有點不方便(例如拿來當作臨時的計算機),所以我自己是另外弄了設定讓他更方便:
- Eval 目前的 sexp,輸出其 eval 結果後,直接自動刪除該 sexp。
- 加上一個
C-u
prefix 就是先按C-u
再按 key-binding 的話,不刪除 sexp,而且會先插入一個箭頭==>
再插入 eval 結果。例如(+ 1 5) => 6
。
- 前面加一個
;; Makes eval elisp sexp more convenient
(defun eval-elisp-sexp ()
"Eval Elisp code at the point, and remove current s-exp
With one `C-u' prefix, insert output following an arrow"
(interactive)
(cond ((equal current-prefix-arg nil) ;if no prefix
(let ((OUTPUT (eval (preceding-sexp))))
(kill-sexp -1)
(insert (format "%S" OUTPUT))))
((equal current-prefix-arg '(4)) ;one C-u prefix
(save-excursion
(let ((OUTPUT (eval (preceding-sexp))))
(insert (format "%s%S" " => " OUTPUT)))))))
(global-set-key (kbd "C-c C-x C-e") 'eval-elisp-sexp)
;; avoid key-binding conflict with org
(define-key org-mode-map (kbd "C-c C-x C-e") 'org-clock-modify-effort-estimate)
- 在
lisp-interaction-mode
中 (例如*Scratch*
) ,可以在一個運算式的最後一個括弧後面按下C-j
直接 eval 並直接將 eval 結果插入當前游標後面。 - 建議可以把
eval-buffer
設定一個快速鍵,可以直接 eval 整個 buffer 方便測試。如:
(global-set-key (kbd "C-c C-e") 'eval-buffer)
(add-hook 'org-mode-hook
(lambda ()
(define-key org-mode-map (kbd "C-c C-e") 'org-export)))
我們常常會需要 eval 一些返回值可能會很長、很混亂的運算式(例如用 Emacs 內建的 json-read-file
來 parse 一個 json 檔案),這時可以使用 M-x pp-eval-last-sexp
,使用方法同 C-x C-e
,但輸出會幫你自動換行、整理得比較漂亮。
雖然如果你以前沒有寫過 Lisp 的話很可能用不到,不過這裡還是順便提一下。
Emacs Lisp 中可以使用 macroexpand
來展開一個 macro,以 defun
為例 ( defun
在 Emacs Lisp 裡面是一個 macro):
(macroexpand '(defun hello (x)
(message "hello, %s" x)))
;; Eval 結果如下:
(defalias (quote hello)
(function (lambda (x)
(message "hello, %s" x))))
給懂 Lisp 的人的解說:這裡也可以順便看到,
defun
的作用實際上是把一個匿名函數lambda
給defalias
到一個symbol
。
Emacs 有內建一個 Emacs Lisp debugger 叫做 edebug
,最常見的用法是拿來給一個 function 除錯:
- 游標移動到一個
(defun ...)
的結尾 M-x edebug-defun
- 在下一次執行該 function 時,該 function 將會變成一個一個 sexp 按順序執行。
- 按
SPC
來執行下一個 sexp - 按
q
離開edebug
- 按
- 再次用一般方法 eval (
C-x C-e
) 該 function,即會恢復回正常狀態。
有時有些錯誤只會讓 minibuffer 跳錯誤訊息,卻不會跳出 *Backtrace*
buffer 來看到底是哪裡出了問題。
可以使用 M-x toggle-debug-on-error
來強迫顯示所有錯誤訊息。
查詢:
- function 用法與文件:
C-h f
。 - variable 定義與文件:
C-h v
。 - 一個 key 在目前 buffer 被 bind 到哪個 function:
C-h k [KEY-BINDING]
注意,如果目前游標下剛好有一串字串符合 function/variable 名稱,會以該字串為預設值,這時只要按 Enter 就可以直接查詢了。
使用 M-x eldoc-mode
。
例如只要輸入 (mapcar
,minibuffer 中就會即時顯示出該 mapcar
的 positional arguments name: mapcar: (FUNCTION SEQUENCE)
使用這個設定讓以後寫 Emacs Lisp 都自動開啟 eldoc-mode:
(add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
C-j
(newline-and-indent
)可以換行並自動縮排(但lisp-interaction-mode
中除外,因為會被解釋成 eval 並輸出結果)。- 在 運算式的最後一個括弧 處
M-C-\
可以將整個運算式自動縮排。 - 覺得自己縮排很麻煩的話,是有個套件叫做
auto-indent-mode
可以很方便的自動縮排就不用手動縮,只是我用起來問題很多就移掉了…想試試看的可以從 MELPA 安裝。
- 務必設定括號突顯;「 沒有這個你根本不可能寫 Lisp 」,語出 Paul Graham (ANSI Common Lisp 第二章)。
(show-paren-mode t)
(setq show-paren-style 'expression)
- 我非常推薦安裝
rainbow-delimiters-mode
,能夠把位在同一層的括號上相同的顏色,再也不會覺得括號很難對齊。(縮排不正確時括弧顏色會出錯,記得C-M-\
。) - 初學 Lisp 時老是有哪裡忘記加括號,eval 時遇到錯誤訊息
End of file during parsing
通常代表你有哪裡括號沒對好,M-x check-parens
可以找到漏掉括弧的地方(前兩者有設定的話,基本上不會遇到這種問題;應該說初學時比較會遇到)。 - 有個終極的 Lisp 括號編輯工具叫做
paredit
,需另外安裝,熟悉的話可以使編輯括號變得更有效率,操作起來就跟變魔術一樣。 似乎有很多人喜歡用這個,只是不好學。我自己是沒在用,詳情可自行 Google。
到目前為止嘗試了不少 Emacs 外掛,我發現我自己是不太偏好「聰明過頭」了的設計…例如 helm, ido, icicles, auto-indent 這類的。 – ono hiroko
- 在 Emacs 裡寫 regexp 時一般應該是沒有問題,但是當你在 Lisp code 裡需要用 regexp 時,例如
(re-search-forward "PATTERN")
,會發現PATTERN
裡反斜線看起來好像無法正常運作。這是因為在 eval 時,裡面整個PATTERN
因為被 double quote 包起來了, 裡面的 regexp 會先被當成是 string 而先解析過一次 (Emacs Lisp 並沒有像是 Python 那種的 raw string)。也就是說,平常只需要一個反斜的話, 在 Lisp code 裡要寫兩個反斜 …
另外,大中小括弧,還有 pipe |
全部都得 escape 掉,不然會被解釋成普通文字而沒有任何 regexp 上的特殊意義。(我不知道為什麼 Emacs Lisp 的 regexp 要這樣設計) 以 Python 為例,我們在要 group match 時原本是寫成 (.+?)
,在 Emacs Lisp 中就得寫成 \\(.+?\\)
(之前就是不知道這點,浪費了很多時間和腦細胞),
- 要寫 Emacs 的 Regexp 時,務必嘗試看看
M-x re-builder
,即時比對 pattern 非常方便。