如何在Emacs中重写被minor mode覆盖的全局键绑定

Emacs中很多minor mode都会定义一个自己的自定义的键绑定方案. 通常定义在一个叫做modename-mode-map的变量里面. 比如自带的帮助是help-mode-map, IELM是ielm-map, 拼写检查的是flyspell-mode-map.

绝大多数情况下是没什么问题的. 但是这次使用Aspell做拼写检查的过程中碰到键绑定冲突的问题. 具体来说就是minor mode的键绑定覆盖了我所定义的全局键绑定.

使用了Aspell之后, 启用拼写检查的方法是"M-x flyspell-mode", 启用之后发现我的C-c已经不能用了. 我在配置文件中已经定义为复制文本了.

 
 
 (global-set-key "\C-c" 'kill-ring-save)
 
 

flyspell-mode的keymap定义如下, 在lisp/textmode/flyspell.el 里面

 
 
  (defvar flyspell-mode-map
    (let ((map (make-sparse-keymap)))
      (if flyspell-use-meta-tab
        (define-key map "\M-\t" 'flyspell-auto-correct-word))
      (define-key map flyspell-auto-correct-binding 'flyspell-auto-correct-previous-word)
      (define-key map [(control ?\,)] 'flyspell-goto-next-error)
      (define-key map [(control ?\.)] 'flyspell-auto-correct-word)
      (define-key map [?\C-c ?$] 'flyspell-correct-word-before-point)
      map)
    "Minor mode keymap for Flyspell mode--for the whole buffer.")
 
 
 

之前的一篇文章里提到了怎么添加键绑定到minor map里面:Emacs中的 return, RET, Enter, Ctrl-m解析

现在我需要的是禁用minor key map里面的某个绑定. 第一个想法是重新定义这个mode-map:

 
 
(add-hook 'flyspell-mode-hook
  (lambda ()
  (defvar flyspell-mode-map
    (let ((map (make-sparse-keymap)))
      (if flyspell-use-meta-tab
        (define-key map "\M-\t" 'flyspell-auto-correct-word))
      (define-key map flyspell-auto-correct-binding 'flyspell-auto-correct-previous-word)
      (define-key map [(control ?\,)] 'flyspell-goto-next-error)
      (define-key map [(control ?\.)] 'flyspell-auto-correct-word)
      ;(define-key map [?\C-c ?$] 'flyspell-correct-word-before-point)
      map)
    "Minor mode keymap for Flyspell mode--for the whole buffer.")
  )
)
 
 

并把不需要的绑定注释掉. 结果不起作用, 因为spellmode模块里面的定义一定会执行, 我估计上面的办法在模块加载之前执行, 加载的过程中还是会被默认的设置覆盖掉.

第二个办法是在hook里面用global-key-set再绑定一次.

 
 
(add-hook 'flyspell-mode-hook
  (lambda ()
  (defvar flyspell-mode-map
    (let ((map (make-sparse-keymap)))
      (if flyspell-use-meta-tab
        (define-key map "\M-\t" 'flyspell-auto-correct-word))
      (define-key map flyspell-auto-correct-binding 'flyspell-auto-correct-previous-word)
      (define-key map [(control ?\,)] 'flyspell-goto-next-error)
      (define-key map [(control ?\.)] 'flyspell-auto-correct-word)
      ;(define-key map [?\C-c ?$] 'flyspell-correct-word-before-point)
      map)
    "Minor mode keymap for Flyspell mode--for the whole buffer.")
     (global-set-key "\C-c" 'kill-ring-save)
  )
)
 
 

也不行, 原因在于global-set-key的性质, 调出这个函数的文档: M-x describe-function global-set-key.

Note that if KEY has a local binding in the current buffer, that local binding will continue to shadow any global binding that you make with this function.

说得很清楚, minor mode里面的会shadow global-set-key的设置, 换句话所, minor mode的优先级更高(那么这个函数名global实在太误导了).

最后的解决方法是在minor mode里面重新绑定一次, 不过是用我们自己的命令.

 
 
(add-hook 'flyspell-mode-hook
  (lambda ()
     (define-key flyspell-mode-map  "\C-c" 'kill-ring-save)
  )
)
 

这个完全可以解决问题, 但是并不是非常直接的方法, 我们真正需要的是阻止minor mode去绑定这个组合键, 现在的做法是用自己的绑定去覆盖minor mode的绑定.