Advising an emacs interactive function: before

蓝咒 提交于 2019-12-05 07:44:08
Trey Jackson

artscan answered his own question with a workable answer, but it's a bit incomplete and misleading. This also involves 'interactive, which can be confusing in and of itself - in that it looks like it is defined inside the body of the command, but is actually used before the function is entered - and before any advice is executed (unless that advice has 'interactive calls...)

The documentation for advice lacks a number of details that would help in this situation, so the better place to look is actually the source: advice.el. Look at that and find the comment section @ Foo games: An advice tutorial. You can also find the source in your Emacs itself with M-x find-library advice RET.

Specifically, for this problem, look at the section in advice.el labeled @@ Advising interactive behavior: - because that's exactly what you're trying to do.

If you read it closely, you'll notice that the advice does not need to be of the form around, but can be before as well, and it can be after - though that's just asking for trouble. This is because the interactive is (and has to be) treated special.

So, the following code works (note the before):

(defadvice find-dired (before eab-find-dired (dir args) activate)
  "ignore find-args, hard code \"-iname '**'\""
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 10)
                      '(find-args-history . 1)))))

Probably a cleaner way to do this, as others suggested, is writing your own function, and I think the easiest is Lindydancer's answer.

Advice is a pretty enticing tool, but is easy to overuse. I wouldn't go as far as saying it is dangerous, but should be used sparingly. It seems to be best used when writing your own function doesn't work - for instance, changing the behavior of a function that is called by code you can't modify. I think good examples of this situation can be found here, here, and here (to toot my own horn).

Emacs picks up the interactive specification before it calls the function.

In general, it is a bad idea to use defadvice, so instead I would suggest that you define your own function and bind it to an appropriate key. For example:

(defun my-find-dired ()
  (interactive)
  (let ((find-args '("-iname '**'" . 10)))
    (call-interactively 'find-dired)))

Of course, you can also simply do the following, if you think that this setting is something that you want for all calls to find-dired:

(setq find-args '("-iname '**'" . 10))

Why do you want to advice an interactive function?

You can easily define your own command

(defun find-dired-my-defaults (dir args)
  "just like `find-dired' but with defaults."
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 1)
                      '(find-args-history . 1))))
  (find-dired dir args))

And if it had been bound in a keymap, you can easily remap it:

(define-key foo-mode-map [remap find-dired] 'find-dired-my-defaults)

The end user almost never has to use defadvice despite what the wiki tells you.

EDIT: @Lindydancer's answer is better in this instance, but I'll leave this answer here to disuade future readers from using defadvice in such contrived ways.

It works:

(defadvice find-dired (around eab-find-dired (dir args) activate)
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 10)
                      '(find-args-history . 1))))
  ad-do-it)

I use interactive form of the function find-dired with substitution: put necessary expression '("-iname '**'" . 10) instead find-args directly in the form. around-advice with arguments (dir args) is used instead of before-advice.

I ran into this issue looking for a simple way to extend the interactive behaviour of eval-last-sexp, describe-function and similar functions without having to write specialized advice functions for each of them. For that purpose I analyzed the call-stack of interactive use using toggle-debug-on-error and a dummy function (defun x () (interactive (list :interactiveform (error "Int")))).

  • Interactive use always involves a call to call-interactively.
  • When a command is executed by pressing a hotkey, the command is passed to command-execute, which calls call-interactively.
  • When executed as M-x COMMAND, the function name is first passed as a string to execute-extended-command (which is the command invoked by M-x), which then calls command-execute, ...

Depending on whether all invocations of the interactive form should be affected by the advise, or only a direct hotkey or M-x invocation, one of these functions can be advised, e.g. using the modern nadvice.el interface:

(defun setup-var-advice (oldfun command &rest r)
  (if (eq command 'save-buffer)
      (let ((myvar t)) (apply oldfun command r)) ;; Advice sets up variable
    (apply oldfun command r))) ;; Advice has no effect

(advice-add #'call-interactively :around #'setup-var-advice)

In this simple case the overhead associated with the advice function is small, but for more realistic situations it is probably important to keep the check, whether the advice should have an effect, as efficient as possible.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!