解决同时使用cider和evil-mode时,company-mode的按键绑定在补全过程中失效问题
背景
这是我很久之前遇到的问题,我同时使用evil-mode和cider,用company-mode进行补全,结果碰到了一个奇怪的现象:在cider作用的buffer中(即cider的REPL
buffer或者clojure-mode buffer等)中用company-mode进行补全,
company-mode的按键绑定会失效,比如我在 company-active-map
中绑定
C-j/C-k分别去选择下一个/上一个候选项,结果在cider的buffer中全部失效,变成触发了evil-mode的按键绑定。
当时在cider的仓库发了Issue:
Key bindings of company-mode don’t work while evil-mode is enabled
很久没有人回复,作者自己也不用evil-mode,估计没有兴趣去管这个Issue,我只好自己去寻找解决方案,利用各种调试手段在company-mode和cider的源码中到处看问题所在,最终找到了问题根源,想出来了一个挺简洁的workaround,当时看doom-emacs用户也有遇到这个问题,于是把workaround发在了doom-emacs的那个Issue里面:
Cider + Company not working as it should
现在整理成一个文章,相当于翻译吧。
问题所在
问题在于cider会频繁执行一个叫 nrepl-bdecode
的函数,它会不停把一个叫
“nrepl-decoding” buffer的major-mode改成 fundamental-mode
(不懂为什么要这么做,因为这个buffer是 get-buffer-create
创建的,Info文档中提到了,该函数创建的buffer一开始就会处于 fundamental-mode
)。把
major-mode改成 fundamental-mode
会触发
after-change-major-mode-hook
,evil-mode在这个hook触发的时候,会去提升它keymap的优先级,提升方法就是:把 evil-mode-map-alist
移到
emulation-mode-map-alists
的最前面(evil-mode的模式编辑以及它自创的各个keymap优先级层次是在 emulation-mode-map-alists
这层模拟的)。
company-mode的 company-active-map
也是在 emulation-mode-map-alists
这一层起作用的,具体的,在每次补全开始的时候,company-mode也会像
evil-mode一样,把自己的 company-emulation-alist
放到
emulation-mode-map-alists
的最前面,其中, company-emulation-alist
的值是 ((t . company-my-keymap))
,而 company-my-keymap
的值会被设置成 company-active-map
,这样,便确保了 company-active-map
有较高的优先级,然后在补全结束的时候,company-mode会设置 company-my-keymap
为nil,不指向 company-my-keymap
,从而使 company-active-map
中的按键绑定失效。总结来说, company-active-map
会在company-mode补全开始的时候生效且有比较高的优先级,会在company-mode补全结束的时候失效,从而不会影响到其他按键绑定。
正常情况下,在company-mode补全过程中,不会有代码去改变major-mode,一般只有在用户打开新的文件,切换buffer等等才会有代码去切换major-mode,这时候才会触发上面的操作。而触发company-mode的补全时,它会提升自己的keymap
到 emulation-mode-map-alists
的最前面,从而在优先级上高于evil-mode的
keymap。但是现在由于cider不停修改"nrepl-decoding"这个buffer的
major-mode,不停触发 after-change-major-mode-hook
,导致evil-mode不停把它的 evil-mode-map-alist
放到 emulation-mode-map-alists
的最前面,于是出现在company-mode补全过程中,evil-mode把自己的keymap优先级提升到比company-mode的keymap还高的情况,从而导致company-mode的按键绑定失效的问题。
workaround
利用“在company-mode补全时, company-my-keymap
会指向
company-active-map
,而在补全结束后它的值会变成nil,即
company-active-map
会在company-mode补全开始的时候生效,会在
company-mode补全结束的时候失效,从而不会影响到其他按键绑定”这点,我们可以给出一个很简单的workaround:
|
|
即在 evil-local-mode
启用的时候,强制把 company-emulation-alist
放到 emulation-mode-map-alists
的最前面(这是
company-ensure-emulation-alist
做的,company-mode自己的函数),由于它的keymap只在补全时生效,所以无条件放到列表最前面是不要紧的。