6

I want to before-advice some function, which uses interactive arguments, e.g. find-dired:

(defadvice find-dired (before eab-find-dired activate)
  (message "before!")
  (setq find-args '("-iname '**'" . 10)))

But emacs executes this advice only after find-dired interactive session and I can't setup find-args before. How to resolve the contradiction?

Upd. Note that defadvice macro is deprecated.

artscan
  • 2,340
  • 15
  • 26
  • 3
    For new readers, please note that `defadvice` is deprecated and you should now use `add-function`, `add-advice` ([and their companion functions](https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Named-Functions.html#Advising-Named-Functions)) – ocodo Nov 07 '15 at 03:35

5 Answers5

9

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).

Community
  • 1
  • 1
Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • Thank you for the instructive and accurate answer! – artscan Jan 30 '13 at 17:31
  • 1
    `defadvice` is dangerous because it's so overused on the wiki. Newbies can easily think it's the only way to customize their Emacs and end up **fundamentally** changing the Emacs API, which causes difficult to debug errors to be reported to package maintainers. It wastes everyone's time. – event_jr Jan 31 '13 at 05:43
  • 1
    For new readers, please note that `defadvice` is deprecated and you should now use `add-function`, `add-advice` ([and their companion functions](https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Named-Functions.html#Advising-Named-Functions)) – ocodo Nov 07 '15 at 03:34
5

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))
Lindydancer
  • 25,428
  • 4
  • 49
  • 68
  • Thank you for the answer, I agree with such example. But I wanted to discuss something like a modification of interactive form: see my answer. – artscan Jan 30 '13 at 16:03
  • @Lindydancer let binding prevents `find-dired` from saving the last used value to `find-args`. You probably want to use `setq` instead. – event_jr Jan 30 '13 at 16:15
  • Ah, right. Well, in that case one could argue if it should be modified at all. Maybe it's better to only set it using `setq` or define something like `my-new-find-dired` that restored it's before calling `find-dired`. Anyway, there is no simple solution as it all starts to depend on the preferences of the user -- do you want to have the last entered expression or ensure that the expression always is restored. – Lindydancer Jan 30 '13 at 16:27
2

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.

event_jr
  • 17,467
  • 4
  • 47
  • 62
  • Thank you for the answer. If I have many keymaps, I have to remap in each of them. I agree: in 99% cases such solution is enough. – artscan Jan 30 '13 at 16:23
1

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.

artscan
  • 2,340
  • 15
  • 26
  • I have been around Emacs for quite some time. This has learned me not to copy-paste unnecessary things, as this tends to break when Emacs change. – Lindydancer Jan 30 '13 at 16:02
  • `defadvice` is dangerous, even if not in this case; it is still a dangerous habit to get into. Don't use it unless you absolutely have to. – event_jr Jan 30 '13 at 16:21
  • I use a few functions with advices in necessary cases. E.g. internal functions, like `ac-inline-show`, which are called inside another functions: their names are already bound. – artscan Jan 30 '13 at 16:44
0

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.

kdb
  • 4,098
  • 26
  • 49