Skip to content

Latest commit

 

History

History
134 lines (100 loc) · 7.62 KB

09-Emacs Lisp開發技巧.org

File metadata and controls

134 lines (100 loc) · 7.62 KB

這篇並不是 Emacs Lisp 教學,而是一些瑣碎筆記。當初不知道這種問題該怎麼問人或問誰,所以一開始寫 Emacs Lisp 時覺得很痛苦。

在這裡分享一些能讓你在開發 Emacs Lisp 時更輕鬆方便的小技巧,需要的人許可以參考。

Eval

  • 在任何 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 快速鍵要拿來作其他用途有點不方便(例如拿來當作臨時的計算機),所以我自己是另外弄了設定讓他更方便:
    1. Eval 目前的 sexp,輸出其 eval 結果後,直接自動刪除該 sexp。
    2. 加上一個 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 出的結果太醜?

我們常常會需要 eval 一些返回值可能會很長、很混亂的運算式(例如用 Emacs 內建的 json-read-file 來 parse 一個 json 檔案),這時可以使用 M-x pp-eval-last-sexp ,使用方法同 C-x C-e ,但輸出會幫你自動換行、整理得比較漂亮。

測試 Macro

雖然如果你以前沒有寫過 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 的作用實際上是把一個匿名函數 lambdadefalias 到一個 symbol

Debug

Debugger

Emacs 有內建一個 Emacs Lisp debugger 叫做 edebug ,最常見的用法是拿來給一個 function 除錯:

  1. 游標移動到一個 (defun ...) 的結尾
  2. M-x edebug-defun
  3. 在下一次執行該 function 時,該 function 將會變成一個一個 sexp 按順序執行。
    • SPC 來執行下一個 sexp
    • q 離開 edebug
  4. 再次用一般方法 eval (C-x C-e) 該 function,即會恢復回正常狀態。

顯示所有錯誤訊息

有時有些錯誤只會讓 minibuffer 跳錯誤訊息,卻不會跳出 *Backtrace* buffer 來看到底是哪裡出了問題。 可以使用 M-x toggle-debug-on-error 來強迫顯示所有錯誤訊息。

Documents

內建文件查詢方法

查詢:

  • function 用法與文件: C-h f
  • variable 定義與文件: C-h v
  • 一個 key 在目前 buffer 被 bind 到哪個 function: C-h k [KEY-BINDING]

注意,如果目前游標下剛好有一串字串符合 function/variable 名稱,會以該字串為預設值,這時只要按 Enter 就可以直接查詢了。

在 Minibuffer 中即時顯示簡易文件

使用 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)

Indent

  • C-j (newline-and-indent)可以換行並自動縮排(但 lisp-interaction-mode 中除外,因為會被解釋成 eval 並輸出結果)。
  • 運算式的最後一個括弧M-C-\ 可以將整個運算式自動縮排。
  • 覺得自己縮排很麻煩的話,是有個套件叫做 auto-indent-mode 可以很方便的自動縮排就不用手動縮,只是我用起來問題很多就移掉了…想試試看的可以從 MELPA 安裝。

Paren

(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

RegExp

  • 在 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 非常方便。