Emacs如何将命令绑定到所有键,而有几个键被豁免?

Emacs如何将命令绑定到所有键,而有几个键被豁免?,emacs,elisp,mode,key-bindings,Emacs,Elisp,Mode,Key Bindings,我想创建一个次要模式(foo模式),它有它的键映射(foo模式映射),但是当用户按下任何不在(foo模式映射)的键时,次要模式应该退出。如何绑定关闭foo模式所有其他键 编辑:这是我根据所选答案提出的解决方案。它也接受数字输入 (defalias 'foo-electric-delete 'backward-kill-word) (defun foo-mode-quit (&optional arg) (interactive) (let ((global-binding (l

我想创建一个次要模式(foo模式),它有它的键映射(foo模式映射),但是当用户按下任何不在(foo模式映射)的键时,次要模式应该退出。如何绑定
关闭foo模式
所有其他键

编辑:这是我根据所选答案提出的解决方案。它也接受数字输入

(defalias 'foo-electric-delete 'backward-kill-word)

(defun foo-mode-quit (&optional arg)
  (interactive)
  (let ((global-binding (lookup-key (current-global-map)
                                    (single-key-description last-input-event))))
    (unless (eq last-input-event ?\C-g)
      (push last-input-event unread-command-events))
    (unless (memq global-binding
                  '(negative-argument digit-argument))
      (foo-mode -1))))

(defvar foo-mode-map
  (let ((map (make-keymap)))
    (set-char-table-range (nth 1 map) t 'foo-mode-quit)
    (define-key map "-" 'negative-argument)
    (dolist (k (number-sequence ?0 ?9))
      (define-key map (char-to-string k) 'digit-argument))
    (define-key map [backspace] 'foo-electric-delete)
    map))

(define-minor-mode foo-mode
  "Toggle Foo mode.
     With no argument, this command toggles the mode.
     Non-null prefix argument turns on the mode.
     Null prefix argument turns off the mode.

     When Foo mode is enabled, the control delete key
     gobbles all preceding whitespace except the last.
     See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)

您必须决定的一件事是,当用户按下另一个键时,您是否应该简单地退出您的模式,或者您是否应该运行绑定到该键的命令(如在
增量搜索


一种方法是使用
pre-command hook
检查
此命令是否是您的命令之一,如果不是,则关闭模式。

解决方案2:您可以使用
设置字符表范围
设置地图中的所有字符。例如:

(defvar foo-mode-map
  (let ((map (make-keymap)))
    (set-char-table-range (nth 1 map) t 'foo-turn-off-foo-mode)
    ...
    map))

我已经包括了一个完整的工作示例,用于创建具有您想要的行为类型的次要模式;关键是在由
make keymap
创建的键映射上使用
set char table range
,该键映射创建了一个具有完整
char table
的密集键映射;在使用
make sparse keymap
创建的稀疏密钥映射上使用此选项将不起作用

(defalias 'foo-electric-delete 'backward-kill-word)

(defun foo-mode-quit (&optional arg)
  (interactive)
  (foo-mode -1))

(defvar foo-mode-map
  (let (map (make-keymap))
    (set-char-table-range (nth 1 map) t 'foo-mode-quit)
    (define-key map [backspace] 'foo-electric-delete)
    map))

(define-minor-mode foo-mode
  "Toggle Foo mode.
     With no argument, this command toggles the mode.
     Non-null prefix argument turns on the mode.
     Null prefix argument turns off the mode.

     When Foo mode is enabled, the control delete key
     gobbles all preceding whitespace except the last.
     See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)

(defvar major-baz-mode-map '(keymap (t . major-baz-mode-default-function)))
为主模式映射设置默认绑定更容易,我在这里包括了这个示例,但正如我在上面提到的,这种备用键映射对于次要模式不起作用:

(defvar major-baz-mode-map '(keymap (t . major-baz-mode-default-function)))
文件中对此进行了讨论,其中指出:

(t . binding)

This specifies a default key binding; any event not bound by other
elements of the keymap is given binding as its binding. Default
bindings allow a keymap to bind all possible event types without
having to enumerate all of them. A keymap that has a default binding
completely masks any lower-precedence keymap, except for events
explicitly bound to nil (see below).
答复3:

我想说,选择的解决方案太复杂了。开始操纵事件队列并不是您通常想要做的事情

相反,我会提出一个完全不同的解决方案。如果始终打开该模式,则可以将backspace等键绑定到一个函数,该函数可以自行决定是按字符操作还是按单词操作

下面是一个简单的概念证明。它仍然有一个显式的函数来进入word模式,但可以将其推广到启用word模式和执行某种操作的函数中

(defun foo-electric-enabled nil
  "Non-nil when foo-mode operate in word mode.")

(defun foo-electric-delete (&optional arg)
  (interactive "p")
  (if (and foo-electric-enabled
           (memq last-command '(kill-region
                                foo-electric-delete
                                foo-enable-word-operations)))
      (call-interactively 'backward-kill-word)
    (setq foo-electric-enabled nil)
    (call-interactively 'backward-delete-char-untabify)))

(defvar foo-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [backspace] 'foo-electric-delete)
    map))

(defun foo-enable-word-operations ()
  (interactive)
  (setq foo-electric-enabled t))

(define-minor-mode foo-mode
  "Toggle Foo mode.
With no argument, this command toggles the mode.
Non-null prefix argument turns on the mode.
Null prefix argument turns off the mode.

When Foo mode is enabled, the backspace key
gobbles all preceding whitespace except the last.
See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)
上面的代码可以进行增强,但我将其作为练习留给读者:

  • 不必检查函数是否是文本列表的成员,可以有一个模块范围的列表,或者,您可以在函数上使用属性,这些函数应该被视为希望保持在word模式

  • 模式线指示灯总是亮着。通过使用另一个次要模式实现此软件包,您可以使用第二个模式来拥有模式行指示器,并指示软件包处于word模式,替换
    foo electric enabled
    foo enable word operations

  • 函数
    foo electric delete
    显式调用
    back kill word
    backword delete char函数。有许多技术可以分别调用绑定到元backspace和backspace的内容


使用
预命令钩子
,因为每次击键都会产生大量开销;当你按下一个键并按住它时,它是非常明显的。。。重复会减慢速度。另外,在使用
self-insert命令时可能会出现问题。退出foo模式时,通过移除挂钩可以轻松处理减速问题。诚然,当foo模式运行时,速度会减慢,但我怀疑这是否真的会产生影响,除非它是一个真正的时间关键包(我非常怀疑)。关于您声称这不应该与
self-insert命令
,您能详细说明一下吗?(我刚刚测试了它,它似乎工作得很好……)减速问题的一个例子是,如果
j
必须
next line
来模拟vi中的导航;使用
pre-command hook
时速度有明显差异。除非将
map
添加为
let
中的最后一个表达式,否则
foo-mode-map
将绑定到最后一个表达式的值is;在您的示例中,这将是
set char table range
或他们为
填写的任何内容返回的值。我必须对上面给出的示例发出严重警告。在实现Emacs包时,能够在不覆盖用户设置的情况下重新加载模块非常重要。如果变量已经有值,则通常
defvar
不会覆盖该变量。上面的代码混合了
defvar
,并显式调用修改函数,如
set char table range
@Lindydancer谢谢,你说得对。我更新了代码,改为使用
let
。在(let…)的第一个参数中丢失了一对括号。这类似于
post-command-hook
解决方案,但更复杂?;)实际上,我使用了一个post-command钩子。我会说,它与
post-command钩子
解决方案非常不同。首先,命令后会运行一个
post-command挂钩
,因此您无法以任何方式更改命令的功能。其次,您将对添加到
后-
预命令钩子
的所有内容受到惩罚,因为您发出的每个命令都必须通过钩子传递。此外,添加到这样一个钩子中的函数不能发出错误(这样做时,它们会被从钩子中无声地删除)。20年的Emacs开发让我学会了远离
pre-
post-command-hook
,除非我真的必须使用它们。