Elisp:有条件地改变键绑定

时间:2013-04-18 18:29:30

标签: emacs elisp

我正在尝试编写一个自定义标签完成实现,它根据点的位置尝试一堆不同的完成。但是,如果没有满足完成的条件,我希望tab能够执行当前模式最初打算执行的操作。

这样的事情:

(defun my-custom-tab-completion ()
  (interactive)
  (cond
   (some-condition
    (do-something))
   (some-other-condition
    (do-something-else))
   (t
    (do-whatever-tab-is-supposed-to-do-in-the-current-mode))) ;; How do I do this?

目前我正在检查特定模式并为该模式做正确的事情,但我真的想要一个能做正确事情的解决方案,而不必为该特定模式明确添加条件。

关于如何做到这一点的任何想法?

谢谢! /埃里克

5 个答案:

答案 0 :(得分:5)

顺便说一下,这是另一个解决方案:

(define-key <map> <key>
  `(menu-item "" <my-cmd> :filter ,(lambda (cmd) (if <my-predicate> cmd))))

答案 1 :(得分:3)

您可以使用key-binding(或其更具体的变体global-key-bindingminor-mode-key-bindinglocal-key-binding)等函数来探测绑定的活动键盘映射。

例如:

(call-interactively (key-binding (kbd "TAB")))
;; in an emacs-lisp-mode buffer:
;;    --> indent-for-tab-command
;; 
;; in a c++-mode buffer with yas/minor-mode:
;;    --> yas/expand

如果你的命令被绑定到 TAB ,一种避免无限循环的方法可能是将你的绑定置于次要模式,并在寻找 TAB 绑定:

(define-minor-mode my-complete-mode
  "Smart completion"
  :keymap (let ((map (make-sparse-keymap)))
            (define-key map (kbd "TAB") 'my-complete)
            map))

(defun my-complete ()
  (interactive)
  (if (my-condition)
      (message "my-complete")
    (let ((my-complete-mode nil))
      (call-interactively (key-binding (kbd "TAB"))))))

答案 2 :(得分:3)

这是我基于Emacs key binding fallback编写的宏,用于有条件地定义键绑定。它将键绑定添加到指定的次要模式,但如果条件不为真,则执行先前分配的操作:

(defmacro define-key-with-fallback (keymap key def condition &optional mode)
  "Define key with fallback. Binds KEY to definition DEF in keymap KEYMAP, 
   the binding is active when the CONDITION is true. Otherwise turns MODE off 
   and re-enables previous definition for KEY. If MODE is nil, tries to recover 
   it by stripping off \"-map\" from KEYMAP name."
  `(define-key ,keymap ,key
     (lambda () (interactive)
        (if ,condition ,def
          (let* ((,(if mode mode
                     (let* ((keymap-str (symbol-name keymap))
                            (mode-name-end (- (string-width keymap-str) 4)))
                       (if (string= "-map" (substring keymap-str mode-name-end))
                           (intern (substring keymap-str 0 mode-name-end))
                         (error "Could not deduce mode name from keymap name (\"-map\" missing?)")))) 
                  nil)
                 (original-func (key-binding ,key)))
            (call-interactively original-func))))))

然后我可以执行以下操作,仅当我在outline-minor-mode的标题上时才使用TAB的特殊绑定。否则执行我的默认操作(我有缩进和yasnippets):

(define-key-with-fallback outline-minor-mode-map (kbd "TAB") 
  (outline-cycle 1) (outline-on-heading-p))

答案 3 :(得分:2)

您可以在没有任何特殊解决方法的情况下实现这一目标。在大多数模式下TAB默认情况下只会缩进,但如果将全局变量tab-always-indent设置为'complete,它将首先尝试完成,如果无法完成则缩进。这通常效果很好,但如果TAB绑定到您主要模式中的另一个命令,则可能运气不佳。

如果它适用于您需要的模式,您只需要在所有适用的缓冲区(可能使用模式挂钩)中将自定义完成功能添加到列表completion-at-point-functions的前面。 completion-at-point命令调用completion-at-point-functions中列出的每个函数,直到其中一个函数返回非nil,因此您需要做的就是让自定义完成函数“落到”现有行为从它返回nil

这不是问题的100%答案,但如果你使用的主要模式是按照正常的指导方针编写的,那么它可能是最干净的方式。

答案 4 :(得分:0)

define-key可以接受引用的字符串或交互式lambdas,如本例所示。

;Static
(define-key evil-normal-state-mapr "m" 'evil-motion-state)
;Conditional
(define-key evil-normal-state-map "m" 
  (lambda () (interactive) (message "%s" major-mode)))

Lambda可以替换为my-tab-completion之类的命名函数,并且可以更有效地使用。

来自define-key的docstring(Emacs 25)

DEF is anything that can be a key's definition:
 nil (means key is undefined in this keymap),
 a command (a Lisp function suitable for interactive calling),
 a string (treated as a keyboard macro),
 a keymap (to define a prefix key),
 a symbol (when the key is looked up, the symbol will stand for its
    function definition, which should at that time be one of the above,
    or another symbol whose function definition is used, etc.),
 a cons (STRING . DEFN), meaning that DEFN is the definition
    (DEFN should be a valid definition in its own right),
 or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP,
 or an extended menu item definition.
 (See info node `(elisp)Extended Menu Items'.)
相关问题