在之前,我们定义的 windows-notepad-mode 通过使用 :keymap 关键字绑定了快捷键,今天我们来讨论它具体做了什么
背景知识
我们使用一个最简单的例子来看看,展开之后的宏是什么样子的:
(define-minor-mode my-test-mode
"测试mode"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c m s") #'my-test-command)
map))宏展开之后,在定义 hook 变量的后面又多了如下内容,这些代码的作用是将我们定义的keymap绑定到一个全局变量中
(defvar my-test-mode-map
(let
((m
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c m s") #'my-test-command) map)))
(cond ((keymapp m) m)
((listp m)
(with-suppressed-warnings
((obsolete easy-mmode-define-keymap))
(easy-mmode-define-keymap m)))
(t (error "Invalid keymap %S" m))))
"Keymap for `my-test-mode'.")在这里可以看到, 前面通过 keymap 关键字返回的变量的被绑定到 m 这个变量名上。后面通过 cond 检查变量类型,如果是真正的 keymap 类型,那么它回直接返回这个变量,如果是list,它会调用 easy-mmode-define-keymap 将它转化为一个 keymap 类型。通过这段代码最终将我们定义的 keymap 绑定到了一个名为 my-test-mode-map 的全局变量中。
在这个宏展开的最后,有这么一句代码:
(add-minor-mode 'my-test-mode 'nil my-test-mode-map nil nil)与之前的代码做一个对比:
(with-no-warnings
(add-minor-mode 'my/focus-mode 'nil
(if (boundp 'my/focus-mode-map)
my/focus-mode-map)
nil nil))如果我们不通过 keymap 定义关键字,它会尝试判断是否有一个类似名字的 keymap 变量。如果有的话,它也会调用 add-minor-mode 进行绑定。
我们查看 add-minor-mode 中有关keymap 的内容:
(when keymap
(let ((existing (assq toggle minor-mode-map-alist)))
(if existing
(setcdr existing keymap)
(let ((tail minor-mode-map-alist) found)
(while (and tail (not found))
(if (eq after (caar tail))
(setq found tail)
(setq tail (cdr tail))))
(if found
(let ((rest (cdr found)))
(setcdr found nil)
(nconc found (list (cons toggle keymap)) rest))
(push (cons toggle keymap) minor-mode-map-alist))))))它的流程如下:
- 判断当前
keymap参数是否有效,如果有效则进入,否则直接退出 - 根据
toggle查找minor-mode-map-alist中是否有对应的mode绑定的keymap,根据前面的描述,这个toggle实际上就是定义的与mode同名的开关变量 - 如果存在这个
keymap,那么它会直接通过setcdr将旧的替换为新的 - 如果需要插入的话,它会遍历整个
alist结构,尝试找到after所在的位置。如果找到的话会将新的keymap插入到after之后。否则直接通过push将keymap插入到alist的最前面。
通过上面的代码,我们知道了 add-minor-mode 中 AFTER 参数的真正用途了。它实际上给我们提供了一种避免新 minor mode 覆盖旧 mode 快捷键的方式。
在我们按下快捷键时,Emacs会按照我们前面介绍的查找顺序依次查找。如果查找到 minor-mode-map-alist,它会根据顺序依次取出 mode 开关变量 my-test-mode 的值,并以此来判断是否需要在对应minor-mode 绑定keymap 中查找。
虽然add-minor-mode 中给出了 after 这种手段让我们能灵活的控制插入的位置,遗憾的是 define-minor-mode 宏并没有指定 after 的方式,它里面将其固定成了 nil。
总结
本节我们就不设置练习了,读者可以尝试自行写一些代码来验证mode 关闭和开启时快捷键的启用与否,以及实验一下全局keymap 和 mode keymap冲突时,Emacs是如何处理的。
本节内容比较简单,实际上 keymap 关键就是定义了一个快捷键的 keymap,并且在此基础上进行了防御编程,防止输入无效的值。根据上面的代码,我们实际上可以不在 keymap 关键字中定义快捷键,在全局定义一个 mode名称-map 的keymap 变量,在最后 add-minor-mode 会自动检查变量是否存在并且帮我们完成绑定。甚至你可以直接操作 minor-mode-alist 变量,手动注入。
评论 (0)