比如说,
(define-advice switch-to-buffer (:after (&rest _) recenter) (recenter))
如何调用未advice的原始的 switch-to-buffer
呢?
问题有歧义,我重新编辑一下:
现在已知是 switch-to-buffer
已经被 Advice 了,可能是 around,也可能是别的其他的,这些函数会在实际调用 switch-to-buffer
的时候被调用。
那么现在我突然想调用最原始的 switch-to-buffer
,就只有依次 advice-remove
所有的 advice,然后调用,然后再加回去。
这种方法在advice非常多的时候不是很好用,而且不是很干净。
我就想问问,在这种情况下有没有办法去调用原始的 switch-to-buffer
?
需要调用原函数(OLDFUN
),你得用 :around
。用 Advice 之前得要看明白 (info “(elisp) Advice combinators”)。
你的描述存在歧义:
- 别人写了一个 advice,但是你想调原来的函数。
- 你正在写一个 advice,过程中需要调用原函数。
第 1 种情况,又可以细分成两种:
-
1.1. 别人添加的 advice 有问题,解决之道是删除
如何一次性删除所有 advice,看这个帖子:Unadvise a function (remove all advice from it) - Emacs Stack Exchange
-
1.2. 别人添加的 advice 没有问题,而且相关的 package 需要,不能删除。但是你自己在某个场景下需要调用原始函数:
(defun foo ()
(message "foo"))
(defun override@foo ()
(message "override@foo"))
(fset 'orignal-foo (symbol-function 'foo))
(advice-add 'foo :override 'override@foo)
(foo) ;; => override@foo
(orignal-foo) ;; => foo
如果是第 2 个意思,应该用 :around
:
(defun around@switch-to-buffer (orign-fn &rest args
;; 如果需要用到具体参数,直接抄原函数的定义:
;; buffer-or-name &optional norecord force-same-window)
)
;; do something before orign-fn
(apply orign-fn args)
;; do something after orign-fn
)
(advice-add 'switch-to-buffer :around 'around@switch-to-buffer)
使用 :around
表示你必须调用原来的函数,以完成工作。否则,就变成了 :override
的效果。
:before
/:after
可以看作是 :around
的特例,这两种情况下自动调用原来的函数。
@xuchunyang @twlz0ne
我的描述确实有歧义,两位理解的都不是我需要的。
(@twlz0ne 的第一种情况应该就是我需要的,不知道有没有办法解决~ )
现在已知是 switch-to-buffer
已经被 Advice 了,可能是 around,也可能是别的其他的,这些函数会在实际调用 switch-to-buffer
的时候被调用。
那么现在我突然想调用最原始的 switch-to-buffer
,就只有依次 advice-remove
所有的 advice,然后调用,然后再加回去。
这种方法在advice非常多的时候不是很好用,而且不是很干净。
我就想问问,在这种情况下有没有办法去调用原始的 switch-to-buffer
?
(defun zx ()
(print 89))
(define-advice zx (:before (&rest _) rece) (print "77"))
(defun around@zx (orign-fn &rest args)
(apply orign-fn args)
(print "around")
)
(advice-add 'zx :around 'around@zx)
(zx)
1 个赞
可是这个好像和 (symbol-function 'foo) 和 advice-add 的位置有关系吧?
如果是先调用 advice-add 就没办法了。。。
明白了你的问题,advice--cdr
貌似可以:
(defun foo () 123)
;; => foo
(define-advice foo (:around (_oldfun) bar)
456)
;; => foo@bar
(foo)
;; => 456
(funcall (advice--cdr (symbol-function 'foo)))
;; => 123
注意我仅试过这一个例子。
但在我看来应该避免这样的问题,Advice 的使用需要节制,像 switch-to-buffer
这样的库函数不适合 Advice,优先重写一个新函数,如:switch-to-buffer-and-recenter
。
3 个赞
yyjjl
10
参考 advice-function-mapc
的实现, 用 advice--cdr
应该可以处理多个 advice 的例子 .
(defun symbol-function-original (symbol)
(let ((function-def (advice--symbol-function symbol)))
(while (advice--p function-def)
(setq function-def (advice--cdr function-def)))
function-def))
(symbol-function-original 'foo)
;; (lambda nil
;; (message "call foo"))
(defun foo ()
(message "call foo"))
(defun foo-ad (orignal-fn &rest args)
(message "call foo-ad")
(apply orignal-fn args))
(advice-add 'foo :around 'foo-ad)
(foo)
;; call foo
;; call foo-ad
(funcall (symbol-function-original 'foo))
;; call foo
2 个赞
cireu
11
advice.el有ad-get-orig-definition
一键获取原始定义
advice--cdr
之前还需要 advice--p
判断一下,否则可能得到错误的返回值:
(with-emacs
(byte-compile
(defun greet (name)
(message "hello, %s" name)))
(advice--cdr (symbol-function 'greet)))
;; => "hello, %s"
显然,上面代码返回的值并非预期的 nil。
问题就出在 byte code 上,如果不进行编译,函数就只是 list:
(with-emacs
(defun greet (name)
(message "hello, %s" name))
(symbol-function 'greet))
;; => (lambda (name) (message "hello, %s" name))
这时 advice--cdr
必然出现异常,也就及时发现了错误。
比较隐蔽的错误是在函数编译之后,因为函数编译之后成了数组。 对数组进行 advice--cdr
或者 aref
操作,得到的是数组当中的元素,也就是函数内部的某一个语句(对于 advice--cdr
来说就是索引为 2 位置的值):
(ith-emacs
(byte-compile
(defun greet (name)
(message "hello, %s" name)))
(symbol-function 'greet))
;; => #[(name) "\303\201\303\202\"\302\207" [name message "hello, %s"] 3]
这里得到的是字符串,所以如果用 funcall 执行也会及时报错。但如果这个位置正好是一个函数的 symbol,可能就稀里糊涂地执行了下去。
随便看看一些内置函数没有 advice 会返回什么:
(with-emacs
(advice--cdr (symbol-function 'switch-to-buffer)))
;; window-normalize-buffer-to-switch-to
(with-emacs
(advice--cdr (symbol-function 'lisp-indent-function)))
;; parse-partial-sexp
最后再用自定义函数完整地检验一遍:
(with-emacs
(require 'cl)
(byte-compile
(defun greet (name)
(message "hello, %s" name)))
;; 必须不是 advice,否则抛出错误
(assert (not (advice--p (symbol-function 'greet))))
(define-advice greet (:override (name) override)
(message "hi, %s" name))
;; 返回原始函数
(and (advice--p (symbol-function 'greet))
(advice--cdr (symbol-function 'greet))))
;; => #[(name) "\303\201\303\202\"\302\207" [name message "hello, %s"] 3]
1 个赞
cireu
13
直接用ad-get-orig-definition
吧,判断工作都给你做好了,就是引用so-called obsolete package(advice.el)显得不优雅而已
(defun ad-get-orig-definition (function) ;FIXME: Rename to "-unadvised-".
(if (symbolp function)
(setq function (if (fboundp function)
(advice--strip-macro (symbol-function function)))))
(while (advice--p function) (setq function (advice--cdr function)))
function)
advice--cdr
就是个aref
的包装,他懂个啥子函数
(defsubst advice--cdr (f) (aref (aref f 2) 2))
ad-get-orig-definition
也不懂函数呀,它只不过是读取了对的位置。
另,这个函数的命名,恐怕通不过 MELPA 的审核,内置的包果然就可以为所欲为。
cireu
15
你说不懂也是对的,毕竟ad-get-orig-definition
遇到非函数会原封不动的返回而不会报错