Emacs Elisp:有条件地更改键绑定

Emacs Elisp:有条件地更改键绑定,emacs,elisp,Emacs,Elisp,我正在尝试编写一个自定义选项卡完成实现,它根据点的位置尝试一系列不同的完成。但是,如果不满足任何完成条件,我希望tab执行当前模式最初打算执行的操作 大概是这样的: (defun my-custom-tab-completion () (interactive) (cond (some-condition (do-something)) (some-other-condition (do-something-else)) (t (do-whate

我正在尝试编写一个自定义选项卡完成实现,它根据点的位置尝试一系列不同的完成。但是,如果不满足任何完成条件,我希望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?
目前,我正在检查特定模式,并为该模式做正确的事情,但我真的希望有一个解决方案,只做正确的事情,而不必为该特定模式显式添加条件

你知道怎么做吗


谢谢/Erik

您可以使用
键绑定
(或其更具体的变体
全局键绑定
次要模式键绑定
本地键绑定
)等功能来探测绑定的活动键映射

例如:

(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绑定时临时禁用其键映射:


你完全不需要任何特殊的解决方法就可以实现这一点。在大多数模式下,默认情况下,
TAB
只执行缩进,但如果将全局变量
TAB始终缩进
设置为
'complete
,它将尝试先完成,如果无法完成,则缩进。这通常工作得很好,尽管如果
选项卡
在您的主要模式之一中绑定到另一个命令,您可能会运气不佳

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


这并不是对这个问题的100%回答,但是如果您使用的主要模式是按照正常的指导原则编写的,那么这可能是最干净的方式。

下面是我编写的一个宏,它基于该宏来有条件地定义键绑定。它将keybinding添加到指定的次要模式,但如果条件不为true,则执行先前分配的操作:

(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))))))
(define-key-with-fallback outline-minor-mode-map (kbd "TAB") 
  (outline-cycle 1) (outline-on-heading-p))
然后,我可以执行以下操作,仅当我处于大纲次要模式的标题上时,才使用特殊的选项卡绑定。否则,将执行默认操作(我有缩进和缩进):

(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))))))
(define-key-with-fallback outline-minor-mode-map (kbd "TAB") 
  (outline-cycle 1) (outline-on-heading-p))

顺便说一句,这里有另一个解决方案:

(define-key <map> <key>
  `(menu-item "" <my-cmd> :filter ,(lambda (cmd) (if <my-predicate> cmd))))
(定义键
`(菜单项“”:过滤器,(lambda(cmd)(if cmd)))

定义键
可以接受引用字符串或交互式lambda,如本例中所示

;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,并且可以更有效地使用

来自定义键的docstring(Emacs 25)


谢谢你的回答!这里的问题是,我想将命令绑定到tab,因此键绑定实际上会返回函数本身,从而导致无限循环。我可以在模式的“原始”键映射中查找绑定吗?你是对的,这不容易做到。有关可能的解决方法,请参阅“我的编辑”。对于次要模式,您不需要从
次要模式映射列表中删除该条目:您只需让绑定次要模式变量(也称为
我的完整模式
)关于调用
键绑定
@Stefan,您能否发布一段代码片段,显示let绑定的外观,以避免删除条目?@Stefan好主意,谢谢。我编辑了我的答案以考虑到这一点。请参阅
定义键
本地设置键
的文档。这通常是通过修改特定于模式的键映射来实现的。这是一个很好的观点,但许多次要模式可能会重新绑定
选项卡
键,例如YASnippet或auto complete。您知道这些次要模式是否符合
(setq tab始终缩进'complete)
?如果要覆盖这些次要模式,请这样做。您仍然可以对制表符命令使用
indent
。顺便说一句,如果
tab always indent
complete
值不适用于yasnippet,您可能希望将其报告为一个bug(在yasnippet中)。您能给出一个如何使用它的示例吗?例如,
应该是什么?什么是
菜单项
/为什么这里需要它?@shrevatsar:
应该是您想要添加(条件)键绑定的任何一个映射。例如,你的主要模式地图<代码>菜单项
只是一个特殊的符号,我需要将它放在工作的地方。最初添加“菜单项+:过滤器”是为了能够动态构建特定(子)菜单(例如,包含缓冲区和帧列表的菜单),因此得名;但是,由于菜单是使用键映射实现的,所以它恰好也适用于非菜单元素。谢谢!这对我很有帮助,而且几乎对我有效。我做了一些类似的事情:
(添加hook'gfm mode hook'(lambda()(progn)(define key gfm mode map(kbd“`”)``(菜单项“markdown insert code:filter”(lambda(cmd)(if mark active cmd)())
)我看到的问题是,当谓词不为true时,键的含义似乎在拾取全局键绑定(
self-insert命令,在本例中为
),而不是特定于模式的命令。@shrevatsar:[请不要引用您的lambdas!]实际上,您所做的是使用条件绑定覆盖特定于模式的绑定,要使用此技术,您需要将条件绑定添加到其他映射。但在您的情况下,这可能不是我推荐的技术。您推荐什么?(据我所知,我的情况正是这个问题的一个特例:我想学习一种在给定模式下使用bin的通用技术