1

Is there a way to wrap a non-interactive function within defadvice so that the new function can accept an interactive argument which can alter the way that the wrapped function gets called?

In other words, suppose there is a standard elisp function called original-function which was not written to run interactively. I'd like to do something like this ...

    (defadvice original-function (around original-function-around activate)
      (interactive "P")
      ;; Check whether a raw argument was supplied, and if so,
      ;; somehow call the original function with non-standard arguments.
      ;; If a raw argument was not supplied, just do the following ...
      ad-do-it)

2 Answers 2

1

Note, that defadvice has been replaced by advice-add since Emacs 24.4.

With advice-add, you could just do the following.

(defun original-function ()
  "Some function without `interactive' specification and with no args."
  (message "Do something."))

;; Cleaning up the mess when repeatedly advicing:
(advice-mapc `(lambda (fun props) (advice-remove 'original-function fun)) 'original-function)

;; That is the actual advice:
(advice-add 'original-function :around
        (lambda (original-fun &optional prefix)
          ;; We use a lambda rather than a named function
          ;; since the named function would not be meaningful as command.
          "The new function with PREFIX argument replacing ORIGINAL-FUN."
          (interactive "P")
          (if prefix
          (message "Do something else.")
        (funcall original-fun))
          )
        )

That also works when the original function has arguments:

(defun original-function (arg &optional opt-arg)
  "Some function without `interactive' specification and with no args."
  (message "Do something with arg %s and opt-arg %s." arg opt-arg))

;; Cleaning up the mess when repeatedly advicing:
(advice-mapc `(lambda (fun props) (advice-remove 'original-function fun)) 'original-function)

;; That is the actual advice:
(advice-add 'original-function :around
        (lambda (original-fun arg &optional opt-arg prefix)
          ;; We use a lambda rather than a named function
          ;; since the named function would not be meaningful as command.
          "The new function with PREFIX argument replacing ORIGINAL-FUN."
          (interactive "sArg:\nsOptional arg:\nP")
          (if prefix
          (message "Do something else with arg %s and opt-arg %s." arg opt-arg)
        (funcall original-fun arg opt-arg))
          )
        )

You can also use the variable current-prefix-arg within the advicing function. Maybe, that is even cleaner because the argument list does not get messed up:

(defun original-function (arg &optional opt-arg)
  "Some function without `interactive' specification and with no args."
  (message "Do something with arg %s and opt-arg %s." arg opt-arg))

;; Cleaning up the mess when repeatedly advicing:
(advice-mapc `(lambda (fun props) (advice-remove 'original-function fun)) 'original-function)

;; That is the actual advice:
(advice-add 'original-function :around
        (lambda (original-fun arg &optional opt-arg)
          ;; We use a lambda rather than a named function
          ;; since the named function would not be meaningful as command.
          "The new function with PREFIX argument replacing ORIGINAL-FUN."
          (interactive "sArg:\nsOptional arg:")
          (if current-prefix-arg
          (message "Do something else with arg %s and opt-arg %s." arg opt-arg)
        (funcall original-fun arg opt-arg))
          )
        )
4
  • Thank you! What if original-function takes args? Suppose it takes two args, arg0 and arg1. Would new-fun be defined to take the following args? ... (original-fun arg0 arg1 prefix)?
    – HippoMan
    Nov 27, 2021 at 23:02
  • Oh, never mind my question about args. The new-fun function will decide which args to pass to original-function.
    – HippoMan
    Nov 27, 2021 at 23:28
  • @HippoMan I modified the answer a bit to use an anonymous command instead of a named command as advice. The named command would not make sense for itself.
    – Tobias
    Nov 27, 2021 at 23:33
  • @HippoMan I also added a version that exploits the native variable current-prefix-arg. That version does not mess around with the arguments of the advising function so much. Nevertheless, the other versions remain valid because they are more general. It could be that you want to add other arguments than just the prefix-arg.
    – Tobias
    Nov 27, 2021 at 23:42
0

This should be all you need, with foo as an example of a non-interactive function you want to advise in the way you say.

(defun foo (&rest args) (message "FOO ARGS: %S" args))

(defun my-advice (orig-fn &rest orig-args)
  (interactive "P")
  (when current-prefix-arg (message "Do something interactive"))
  (apply orig-fn orig-args)))

(advice-add 'foo :around #'my-advice)

The command that's the interactive advised function takes a prefix arg as its only arg. (You could of course have it take more args, if you wanted that.)


But you also said that with a prefix arg you want to invoke the advised function with different args. For that, you just pass those args instead of orig-args, when you invoke the function with apply. You didn't specify what you want for that conditional behavior, but that's the idea.

(defun my-advice (orig-fn &rest orig-args)
  (interactive "P")
  (apply orig-fn (if current-prefix-arg
                     (some-alternative-list-of-args) ; <=======
                   orig-args)))

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.