【问题标题】:Is there a `flet` for commands in Emacs?Emacs 中是否有用于命令的“flet”?
【发布时间】:2017-07-20 16:06:36
【问题描述】:

我想动态地将一个命令重定向到另一个命令 使用周围建议的某些功能,如下所示:

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

但是,这会产生错误(wrong-type-argument commandp f1), 表示当flet重新定义f1函数时,它没有 处理交互式表单并将其视为命令,因此它不能 由call-interactively 调用。

是否有flet 的变体可以以这种方式用于命令?

(这是我想要做的实际重新定义:)

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

【问题讨论】:

    标签: emacs elisp


    【解决方案1】:

    您在 flet 中遇到了一个愚蠢的错误:flet 的宏扩展将具有:(lambda (&rest args) (progn (interactive) (call-interactively 'f2)))。请注意其中添加的虚假progn,它“隐藏”了interactive

    要获得更多控制权(同时避免使用 cl.el),您可以这样做:

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

    【讨论】:

      【解决方案2】:

      (编辑:cl-letf 宏可以在现代 emacs 中本地执行此操作。下面的答案可能对旧版本仍然有用。)

      好吧,如果以前没有,现在有:

      (require 'cl)
      (require 'cl-lib)
      (defmacro command-let (bindings &rest body)
        "Like `flet', but works for interactive commands.
      
      In addition to the standard `(FUNC ARGLIST BODY...)' syntax from
      `flet', this also supports `(FUNC NEW-FUNC)' as a shorthand for
      remapping command FUNC to another command NEW-FUNC, like this:
      
        (defun FUNC (&rest ignored)
          (interactive)
          (call-interactively NEW-FUNC))
      
      \(fn ((FUNC ARGLIST BODY...) ...) FORM...)"
        (declare (indent 1))
        (cl-loop for binding in bindings
                 collect (list (car binding) nil) into empty-bindings
                 collect (if (symbolp (cadr binding))
                             ;; Remap one command to another
                             `(defun ,(car binding) (&rest args)
                                (interactive)
                                (call-interactively ',(cadr binding)))
                           ;; Define command on the fly
                           (cons 'defun binding))
                 into defun-forms
                 finally return
                 `(flet (,@empty-bindings)
                    ,@defun-forms
                    ,@body)))
      

      在行动:

      (defadvice myfunc (around f1-to-f2 activate)
        (command-let ((f1 f2))
          ad-do-it))
      (myfunc)
      

      该代码现在根据需要使用call-interactively 调用f2 命令。

      【讨论】:

      • (require 'cl-lib) 是不够的,因为flet 不是由 cl-lib 提供的(而是由 cl.el 提供)。
      • 感谢您的提醒。 cl-lib 用于循环宏,我忘记了flet 不是内置的。此外,我一直忘记哪些flet 变体使用动态绑定,哪些使用词法绑定。显然我这里需要动态绑定。
      • @RyanThomson:是的,它相当混乱/令人困惑。它们背后的故事有帮助,虽然:cl-lib 更新,因此 cl-fletflet 更新,因此 cl-flet 是词法的“自然”(因为词法范围在 Emacs 中更新)。至于letff 来自setf 而不是来自“let 函数”,所以它实际上只是一种“设置...恢复”的方式,因此是动态范围。
      猜你喜欢
      • 2015-01-25
      • 1970-01-01
      • 2010-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多