Search code examples
emacselisp

Is there a `flet` for commands in Emacs?


I want to dynamically redirect one command to another one within a certain function using around advice, something like this:

(defun f1 (arg)
  (interactive (list (read-from-minibuffer "F1: ")))
  (message "f1: %S" arg)
  arg)
(defun f2 (arg)
  (interactive (list (read-from-minibuffer "F2: ")))
  (message "f2: %S" arg)
  arg)
;; Function that invokes the f1 command
(defun myfunc ()
  (call-interactively 'f1))

;; I want myfunc to invoke f2 instead whenever it would invoke f1
(defadvice myfunc (around f1-to-f2 activate)
  (flet ((f1 (&rest args) (interactive) (call-interactively 'f2)))
    ad-do-it))

(myfunc)

However, this gives an error (wrong-type-argument commandp f1), indicating that when flet redefined the f1 function, it didn't process the interactive form and treat it like a command, so it can't be invoked by call-interactively.

Is there a variant of flet that will work for commands in this way?

(Here are the actual redefinitions I want to do:)

(defadvice org-metaleft (around osx-command activate)
        (flet ((backward-word (&rest args)
                (interactive)
                (call-interactively #'move-beginning-of-line)))
          ad-do-it))

(defadvice org-metaright (around osx-command activate)
        (flet ((forward-word (&rest args)
                (interactive)
                (call-interactively #'move-end-of-line)))
          ad-do-it))

Solution

  • You're bumping into a silly bug in flet: the macroexpansion of flet will have: (lambda (&rest args) (progn (interactive) (call-interactively 'f2))). Notice the spurious progn added in there, which "hides" the interactive.

    To get more control (and avoid cl.el at the same time), you can do:

    (defadvice myfunc (around f1-to-f2 activate)
      (cl-letf (((symbol-function 'f1)
                 (lambda (&rest args)
                   (interactive) (call-interactively 'f2))))
        ad-do-it))