psearch: 基于 pcase 的 elisp 代码搜索工具

比如在包 doom-modeline 中,函数 doom-modeline-segment--matches 是通过下面这个宏定义的:

(doom-modeline-def-segment matches
  "Docstring"
  (let ((meta (concat (doom-modeline--macro-recording)
                      (doom-modeline--anzu)
                      (doom-modeline--phi-search)
                      (doom-modeline--evil-substitute)
                      (doom-modeline--iedit)
                      (doom-modeline--symbol-overlay)
                      (doom-modeline--multiple-cursors))))
    (or (and (not (string-empty-p meta)) meta)
        (doom-modeline--buffer-size))))

我的 patch 代码没有什么特别,比如,就使用:

(psearch-patch doom-modeline-segment--matches
    (psearch-replace '`meta
                     '`meta_))

但最终的结果是:并没有重新定义 doom-modeline-segment--matches 这个函数,而是定义了一个新的函数 doom-modeline-segment--doom-modeline-segment--matches,而这,就是问题所在。

我明白了。

问题虽然已经明了,但是解决起来有点麻烦。

普通函数和 doom-modeline-def-segment 生成的函数是不一样的:

(psearch-patch--find-function 'doom-modeline-segment--foobar)
;; => (doom-modeline-def-segment foobar "Docstring" body...)
;;                    |             |         |        |
;;                    |             |         |        `--- [3]函数体
;;                    |             |         `------------ [2]文档
;;                    |             `---------------------- [1]函数名
;;                    `------------------------------------ [0]声明符

(psearch-patch--find-function 'foobar)
;; => (defun foobar nil "Docstring" body...)
;;       |     |     |       |        |
;;       |     |     |       |        `--- [4]函数体
;;       |     |     |       `------------ [3]文档
;;       |     |     `-------------------- [2]参数表
;;       |     `-------------------------- [1]函数名
;;       `-------------------------------- [0]声明符

从以上对比可以看出,doom-modeline-def-segment 定义函数是没有参数列表的,所以它的 Docstring 位置和 defun 定义的函数不一样:

而且这个不一样还doom-modeline-def-segment 独有的, 换个其它 xxx-def-yyy 有可能就不一样了。有的可能需要参数列表,有的可能不需要 Docstring。这就导致其生成的结构无法预料

需要有一种方法,可以让用户指定如何解析 def 生成的结构,这无疑会给用户增加负担。还要考虑到不改变 psearch-patch 的声明格式,不影响旧的 patch 代码,或许可以在外层套个 let:

(let ((psearch-patch-symbol-definition-pospec
       '((symbol-name . 1)
         (docstring . 2))))
  (psearch-patch doom-modeline-segment--matches
    (psearch-replace '`meta
                     '`meta_)))

让我再想一想,有可能指定一个 (let ((psarch-patch-docpos 2)) ...) 就够了。

1 个赞

真是很漂亮、精确和简洁的解释!没有认真读过前辈的代码,但是通过这个回答,学到了不少!

再次感谢这个很实用的工具!

同时,我有两个不太相关的小问题:

  1. 这部分的注释,是怎么排版成这种“图形化”的纯字符呢?有什么小工具吗?
  1. 我之所以会尝试使用如下“看似没用”的代码:

是因为,我 patch 了 doom-modeline--multiple-cursors 这个函数(它是用 defsubst 定义的),并且被 doom-modeline-segment--matches 调用。

但是仅仅做这个 patch 没有效果(是因为内联?还是 Doom Emacs 的黑魔法?),并且我发现,只要我重新求值一下 doom-modeline-segment--matches 的定义,就有效果了。(并且先 patch 和先重新求值皆可)

这才有了我上面这个“看似没用”的代码。但最终我是通过这个代码来强制重新求值的:

(eval (psearch-patch--find-function 'doom-modeline-segment--matches))

所以,前辈有遇到类似的情况吗?有相关原因和更漂亮的解决方案吗?

手打,没工具。

内联。

最新版本 Add support for macro-generated function · twlz0ne/psearch.el@b027a9d · GitHub 应该可以支持 doom-modeline-segment 函数了,用法:

(let ((psearch-patch-function-definition-docpos 2))
  (psearch-patch doom-modeline-segment--matches
    (psearch-replace '`MATCH-PATTERN
                     '`REPLACEMENT)))
1 个赞

嗯嗯,学到了!感谢!

我尝试这个版本发现,可以 patch defun 定义的函数,但是无法 patch defsubst 定义的函数呢(该提交之前,patch 功能是正常的)。

所以是不是把 defsubst 考虑进去比较好:

@@ -797,7 +797,7 @@
     `(let ((func-def (psearch-patch--find-function ',orig-func-spec)))
        (with-temp-buffer
          ;; Modifiy function name
-         (when (and (eq 'defun (nth 0 func-def)) (not (eq ',name (nth 1 func-def))))
+         (when (and (memq (nth 0 func-def) '(defun defsubst)) (not (eq ',name (nth 1 func-def))))
            (setcdr func-def (cons ',name (nthcdr 2 func-def))))
          (print func-def (current-buffer))
          ;; Modify docstring.
@@ -805,7 +805,7 @@
          (down-list)
          (forward-sexp
           (+ ,docpos (if (equal 'lambda (sexp-at-point)) 0
-                       (if (memq (sexp-at-point) '(defun cl-defgeneric cl-defmethod)) 1
+                       (if (memq (sexp-at-point) '(defun defsubst cl-defgeneric cl-defmethod)) 1
                          0))))
          (let ((str "[PATCHED]"))
            (goto-char (car (bounds-of-thing-at-point 'sexp)))

defubst 本身就算改了定义也没法反映到已经编译的函数里,没啥做的意义

是这样的,但是有时候确实需要改动一些内联函数,所以我会把某些受影响的函数重新求值一下(假设 FUN 调用了某个被 patch 的内联函数):

(eval (psearch-patch--find-function 'FUN))

能否提一个pr?同时记得修改一下头注释把你的名字加上去:

;; Last-Updated: <当下的时间戳>
;;           By: <你的名字>

嗯呢 已经提了,请看看有没有问题:-)

1 个赞

已合并。​​​​​