Emacs 每加载一个文件,都会把其中定义的变量、函数等等记录到 load-history
:
("/path/to/foo.el"
(defun . foo-fun1)
(defun . foo-fun2)
...)
这就是跳转的关键。通过这些信息,定位到文件,然后查找定义所在。
然而,如果 defun
语句写在另一个函数里面,Emacs 是不会有任何记录的。(这种情况在我的配置中有不少,为此我写了几个函数,在必要时扫描配置文件,生成结果作为 load-history
的补充。细节暂不表。)
还有另一种情况:如果一个函数连名字都是在另外一个宏里拼接的,Emacs 就更不会记录了:
(defmacro def-greeting (name)
"Generate a greeting function."
`(defun ,(intern (format "hello-%s" name)) ()
,(format "Say hello to ‘%s’." name)
(message "hello, %s" ',name)))
(def-greeting foo)
;; => hello-foo
这就不能简单扩充 load-history
了,因为它要求具体的名称以便查找,而上边的例子当中并没有 hello-foo
字样出现。除非改造跳转的查找方式,然后在 load-history
里边记录:
("/path/to/greeting.el"
(defun . (hello-foo :search-pattern "(def-greeting foo)")))
后续还有不少工作要做,与其这样不如在 docstring 记录下相关信息(反正也是要写 docstring 的),这样即便不跳转到定义,在 describe-function
也能得到足够的信息:
(defun gen-docstring ()
"Generate docstring."
(let* ((fram (backtrace-frame 5))
(defn (nth 2 fram))
(file (or load-file-name (buffer-file-name))))
(format "This function is generated by `%s' in file `%s:%s'."
(car defn) file defn)))
(defmacro def-greeting (name)
"Generate a greeting function."
(let ((gdoc (gen-docstring)))
`(defun ,(intern (format "hello-%s" name)) ()
,(concat (format "Say hello to ‘%s’.\n\n" name) gdoc)
(message "hello, %s" ',name))))
(def-greeting foo)
;; => hello-foo
让 gen-docstring
生成的信息可跳转(参考了 @xuchunyang 添加个删除 Advice 函数的按钮):
(define-advice describe-function-1 (:after (function) add-jump-link)
"Add a jump link."
(when (get-buffer "*Help*")
(with-current-buffer "*Help*"
(save-excursion
(goto-char (point-min))
(when (re-search-forward "This function is generated by ‘[^’]+’ in file ‘\\(\\([^:]+\\):\\([^’]+\\)\\)’." nil t)
(let ((file (match-string 2))
(defn (match-string 3))
(inhibit-read-only t))
(replace-match "" nil nil nil 1)
(insert-text-button
file
'cursor-sensor-functions `((lambda (&rest _) (message "%s" ',defn)))
'help-echo (format "%s" defn)
'action
`(lambda (_)
(find-file-other-window ,file)
(goto-char (point-min))
(search-forward ,defn))
'follow-link t)))))))