2015-04-16 43 views
4

我想一個命令動態地重定向到一個 某些函數中另一個周圍使用建議,這樣的事情:在Emacs中有沒有「flet」命令?

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

回答

4

您碰到了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命令。

+1

'(需要「CL-LIB),因爲'flet'不提供'是不夠的cl-lib(而不是cl.el)。 – Stefan

+0

感謝您的提醒。 'cl-lib'用於循環宏,我忘記了'flet'沒有內置。另外,我一直在忘記哪個'flet'變體使用動態綁定,哪些使用詞法綁定。顯然我需要在這裏動態綁定。 –

+0

@RyanThomson:是的,這很雜亂/混亂。它們背後的故事有助於:tho:'cl-lib'更新,因此'cl-flet'比'flet'更新,所以'cl-flet'的「自然」是詞法的(因爲詞法範圍更新在Emacs中)。至於'letf','f'來自'setf'而不是來自「let function」,所以它只是一種「設置...恢復」的方式,因此是動態範圍。 – Stefan