2

Using advice-add from nadvice.el, I am trying to add a :filter-args advice to an interactive function. Is it somehow possible for it to detect, whether the advised function has been called interactively?

I was trying to use called-interactively-p, but M-x describe-function called-interactively-p says:

This function is very brittle, it may fail to return the intended result when the code is debugged, advised, or instrumented in some form. [...]

Instead of using this function, it is cleaner and more reliable to give your function an extra optional argument whose ‘interactive’ spec specifies non-nil unconditionally ("p" is a good way to do this), or via (not (or executing-kbd-macro noninteractive)).

Sadly, neither works:

  • The brittleness of called-interactively-p makes it useless here; Only around advise will detect interactiveness correctly with called-interactively-p, but then the original function doesn't see the interactive call that way anymore.
  • The suggested workaround is actually wrong. noninteractive determines whether the emacs session is interactive, not whether the command was called interactively.

Minimal working example for called-interactively-p

(defun tmpdemo-command (&rest args)
  (interactive '(was called interactively))
  (message "tmpdemo-command:     %-3S %-3S" 
    (called-interactively-p)
    (not (or executing-kbd-macro noninteractive)))
  (message "%S" args))

(advice-add #'tmpdemo-command :around #'tmpdemo-around)
(advice-add #'tmpdemo-command :filter-args #'tmpdemo-filter-args)

(defun tmpdemo-around (oldfun &rest args)
  (message "tmpdemo-around:      %-3S %-3S"
    (called-interactively-p)
    (not (or executing-kbd-macro noninteractive)))
  (apply oldfun args))

(defun tmpdemo-filter-args (args)
  (message "tmpdemo-filter-args: %-3S %-3S"
    (called-interactively-p)
    (not (or executing-kbd-macro noninteractive)))
  args)

When executing it interactively as M-x tmpdemo-command:

tmpdemo-filter-args: nil t      
tmpdemo-around:      t   t  <-- Only the around advise sees the
tmpdemo-command:     nil t      expected result.
(was called interactively)

When running it non-interactively as (tmpdemo-command 'noninteractive)

tmpdemo-filter-args: nil t  <-- the suggested workaround gives
tmpdemo-around:      nil t      the wrong result!!
tmpdemo-command:     nil t  
(noninteractive)
  • 2
    You've misunderstood the noninteractive suggestion, the idea is to do (defun tmpdemo-command (interactive-call &rest args) (interactive (list (not (or executing-kbd-macro noninteractive)) 'etc)) ...) and then check the interactive-call argument instead of using called-interactive-p. That suggestion is not applicable if you're trying advise a function that's already using called-interactive-p (i.e., not following the suggestion) though. – npostavs Dec 6 '19 at 13:05
  • @npostavs - I am trying to advise shell-command, so mostly the problem is, that I can't just add an argument without breaking things. – kdb Dec 6 '19 at 13:18
  • "I can't just add an argument" - Right. But as far as I can tell shell-command doesn't use called-interactively-p, so it seems like you may have asked the wrong question. – npostavs Dec 6 '19 at 14:04
  • @npostavs The main problem is that the advise has no reliable way to detect an interactive call, when the interface of the original function cannot be changed. I mostly just meant to show, that even trying to may cause further breakage, e.g. when a function has two :around advises -- only one of them will be able to detect interactive calls, and they may not know if each other (e.g. user configuration and a package-provided advise). – kdb Dec 6 '19 at 16:38
1

As npostavs explains in a comment, the noninteractive suggestion is to do:

(defun foo (&optional interactivep)
  (interactive "p")
  ...
  ...use `interactivep' when you want to know if the call was interactive.
  ...)

which corresponds to (called-interactive-p 'any) or

(defun foo (&optional interactivep)
  (interactive (list (not (or executing-kbd-macro noninteractive))))
  ...
  ...use `interactivep' when you want to know if the call was interactive.
  ...)

which corresponds to (called-interactive-p 'interactive), or even:

(defun foo (&optional interactivep)
  (interactive "p")
  ...
  ... use `(and interactivep
  ...           (not (or executing-kbd-macro noninteractive)))'
  ... when you want to know if the call was interactive.
  ...)

Note that you can also make the :around case work "correctly" (with the usual caveat that called-interactively-p remains brittle) as follows:

(defun tmpdemo-around (oldfun &rest args)
  (message "tmpdemo-around:      %-3S %-3S"
           (called-interactively-p)
           (not (or executing-kbd-macro noninteractive)))
  (apply (if (called-interactively-p 'any)
             #'funcall-interactively #'funcall)
         oldfun args))
| improve this answer | |
  • I.e., it is possible to make called-interactively-p work correctly in an around advise in such a manner that for the function nothing changes. But that still leaves the advise vulnerable to an incorrect result from called-interactively-p caused by an additional :around advise, correct? – kdb Dec 6 '19 at 16:40
  • @kdb: That's right. The inherent brittleness can still bite you ;-) – Stefan Dec 6 '19 at 17:16
  • There is also the issue that around advice is pretty awful for debugging - try using trace-function with it :/ – kdb Dec 7 '19 at 9:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.