一鍵跳轉到定義!highlight-symbol增強攻略

Emacs 里有一個非常好用的包,叫做 highlight-symbol,用於高亮當前所在的 symbol(通常是變數名或者函數名),並可在全 buffer 里該 symbol 出現過的所有地方任意跳轉,同時還帶計數功能,強大且快捷,可以說是裝機必備。

然而 highlight-symbol 有一個比較明顯的缺陷,其也經常被認為是 Emacs 這款編輯器的缺陷,那就是不支持跳轉到函數定義。一般而言,當一個腳本的代碼量超過200行之後,跳轉到定義這種需求就變得十分強烈。那麼,既然 highlight-symbol 已經提供了非常不錯的跳轉功能了,我們何不在此基礎上組合優化一下,實現大家切實需要的跳轉到定義的功能呢?

首先來優化一下 highlight-symbol 這個函數,在當前游標處存在 symbol 時進行高亮,該高亮行為可以對不同的 symbol 重複進行,高亮成不同的顏色。當不存在 symbol 或者當前 symbol 已經高亮時,就取消全部高亮而不是報錯。

(defun c-highlight-symbol ()n (interactive)n (unless (minibufferp)n (let ((s (highlight-symbol-get-symbol)))n (if (or (not s) (highlight-symbol-symbol-highlighted-p s))nt (highlight-symbol-remove-all)nt(highlight-symbol)nt(setq symbol/pos (point))))))n

注意這裡有一個新的變數,叫做 symbol/pos,這是我特地定義的用於保存最近一次高亮或跳轉的 symbol 位置的變數,主要是方便在離開高亮區進行編輯後,再快速跳回原位。對應的,需要對前後跳轉的函數進行相應的修改:

(defun c-highlight-symbol-next ()n (interactive)n (unless (minibufferp)n (highlight-symbol-jump 1)n (setq symbol/pos (point))))nn(defun c-highlight-symbol-prev ()n (interactive)n (unless (minibufferp)n (highlight-symbol-jump -1)n (setq symbol/pos (point))))n

在這兩個用於前後跳轉的函數中,規定了每一次跳轉行為都會更新 symbol/pos 為當前位置,當你臨時到附近進行編輯操作或者跑到不知道哪裡去之後,可以利用下面的函數回到最近更新的位置,即 symbol/pos:

(defun c-highlight-symbol-recall ()n (interactive)n (when symbol/posn (goto-char symbol/pos)))nn(defvar symbol/pos nil)n(make-variable-buffer-local symbol/pos)n

要注意該變數一定要將其初值設為 nil,否則如果設為0的話,就直接跳到 buffer 開頭了。另外由於所有跳轉都是在當前 buffer 的行為,所以還需要將其設為 buffer-local。

那麼好,有了這幾個函數的熱身之後,我們便可以輕易的實現跳轉到定義的功能。我把這個函數命名為 c-highlight-symbol-definition,這裡的定義可以是函數定義 (defun),可以是變數定義 (defvar) ,以及其他各種各樣的定義如 defcustom defface defmacro 等等,反正它們都是 "def" 開頭的:

(defun c-highlight-symbol-definition ()n (interactive)n (unless (minibufferp)n (let ((p t) (pt (point)) (s (highlight-symbol-get-symbol)))n (when s (highlight-symbol-count s t))n (unless (f-highlight-symbol-def-p s)nt(setq symbol/pos pt)nt(while (and p (not (f-highlight-symbol-def-p s)))nt (highlight-symbol-jump 1)nt (when (= pt (point)) (setq p nil)))))))nn(defun f-highlight-symbol-def-p (symbol)n (save-excursionn (beginning-of-line)n (looking-at-p (concat symbol/def symbol))))nn(defvar symbol/def "(?def[a-z-]* ")n(make-variable-buffer-local symbol/def)n

其代碼邏輯和上一期講到的 buffer 切換類似,先寫一個簡單的函數 (f-highlight-symbol-def-p) 用於判斷當前是否處於定義處,這個通過簡單的正則匹配就可以了。然後再寫一個循環不停跳轉直到抵達定義處,或者回到原點,然後跳出循環。如此一來,我們便在 highlight-symbol 這個插件的基礎上,成功實現跳轉到定義的功能了,除此以外,你還可以在看完了它的定義之後,再通過 c-highlight-symbol-recall 一鍵返回到之前的位置。

這裡還需要引起注意的是,對於不同的語言,用於函數定義的語法是各不相同的,你需要結合自己的需求,有針對性的修改這裡用於正則匹配的字元串 symbol/def,然後添加到相應 Major-Mode 的 hook 中去。例如這裡的 "(?def[a-z-]* "),其實已經滿足 Elisp 和 Python 的要求了。

當然,這個方法仍有一定的局限性,那就是不支持跨 buffer 跳轉。好在暫時我是不需要的,以後看有時間有心情再考慮實現一下吧。


推薦閱讀:

SICP換零錢迭代方法實現,是如何寫的?
入門 Lisp 有哪些在線資料?
精通 Lisp 是一種怎樣的體驗?
Lisp的精髓是什麼?
Lisp可以完成哪些其它語言難以實現的功能?最好能夠舉一些例子

TAG:Emacs | Lisp | 文本编辑器 |