丢掉evil-collection,融合emacs和evil。

用Evil Collection,在emacs和evil按键存在冲突时,只能迁就一个,不能两全。典型的场景是magit中jk是evil的操作,hl是emacs的操作。这太别扭了。
我想到一种解决思路,同时保留两套按键。evil像是悬浮在emacs上面,所有按键都是evil的操作。然后定义一个前缀键(define-prefix-command),按下前缀键之后可以穿透evil,继续按键就是emacs的操作。

具体实现如下:

(use-package evil :ensure t
  :init
  (setq evil-disable-insert-state-bindings t) ;; insert state保留emacs键绑定。
  (setq evil-want-keybinding nil) ;; 不要执行evil-keybindings.el,evil-keybindings.el的作用和Evil Collection类似。
  (setq evil-want-C-i-jump nil) ;; 把TAB键释放出来。
  :config
  (setq evil-emacs-state-modes '(ediff-mode color-rg-mode)) ;; ediff-mode和color-rg-mode不需要evil。
  (add-hook 'with-editor-mode-hook 'evil-insert-state) ;; 可编辑模式默认insert state。
  (evil-mode))


;; 使用define-prefix-command实现类似spacemacs键绑定
(define-prefix-command 'space-leader-map)
(keymap-set evil-motion-state-map "SPC" 'space-leader-map)
(keymap-set evil-normal-state-map "SPC" 'space-leader-map)

(evil-define-key nil space-leader-map
  (kbd "SPC") 'execute-extended-command
  (kbd "RET") 'consult-bookmark
  "0" 'treemacs-select-window
  ;; buffer
  "bb" 'consult-buffer
  "bi" 'ibuffer
  "bd" 'evil-delete-buffer
  "bl" 'evil-switch-to-windows-last-buffer
  "bx" #'(lambda () (interactive) (switch-to-buffer "*scratch*"))
  ;; 省略更多键绑定
  ;; quit
  "qr" 'restart-emacs
  "qq" '("Quit" . save-buffers-kill-terminal))

;; 最关键一步。在normal模式下按 SPC + m 会进入当前mode的major-mode-map,继续按键就是emacs的操作了。
(add-hook 'buffer-list-update-hook #'(lambda () (interactive) (keymap-set space-leader-map "m" (symbol-value (intern-soft (format "%s-map" major-mode))))))

;; 之前已经把TAB键释放出来,再把RET和q释放出来。
(define-key evil-motion-state-map (kbd "RET") nil)
(evil-define-key 'normal text-mode-map (kbd "RET") #'embark-dwim) ;; embark-dwim相当于vscode中 Ctrl + 鼠标左键。
(evil-define-key 'normal prog-mode-map (kbd "RET") #'embark-dwim)
(evil-define-key 'normal special-mode-map "q" #'quit-window)

这套模式我已经用了一段时间了,很舒服。比如在magit中,hjkl就是evil的方向键。然后按 SPC + m + s 进行stage。多个文件需要state话,移动到对应文件,按 .(evil-repeat)。按 SPC + m + c + c 进行提交。

总结一下,默认所有按键都是evil操作,需要emacs操作时,先按 SPC + m ,再按emacs中对应的键绑定。
配合which key,按下 SPC + m,会呈现出当前major mode的快捷键提示。

我的完整配置看这里: .emacs.d/lisp/key.el at main · ltylty/.emacs.d · GitHub

1 个赞

evil本身有emacs mode啊

但是要来回切换,很麻烦。我这样操作可以一直保持在normal state。

evil自带的emacs mode就是这样切换的呀

1 个赞

你这个是临时启动用一下 emacs 下的键绑定吗?evil 有一个 evil-execute-in-emacs-state 默认绑定的 \ 键,可以试一试。

2 个赞

有个general.el 的包,可以很好的适配这个场景。下面就是我在use-package中利用general 来实现和你类似的功能,我只是用SPC 作为general leading key,“,” 用做major-mode leader key,主要是为了模拟spacemacs的逻辑。同时,我还是整体保留了evil-collection,因为它们在很多场景的按键配置比较不错。

1 个赞

原来有这个功能,我还费劲自己弄半天 :rofl:
这句话我还是翻了which key的代码,花了几个小时才写出来。

(add-hook 'buffer-list-update-hook #'(lambda () (interactive) (keymap-set space-leader-map "m" (symbol-value (intern-soft (format "%s-map" major-mode))))))

我也尝试过这个搭配。但是general.el在message buffer和某些不常见的buffer会失效,不太清楚原因。
最后选用emacs自带的define-prefix-command实现了。

1 个赞

message buffer 有个 hack:

  ;; fix message buffer https://github.com/noctuid/general.el/issues/493
  (add-hook 'after-init-hook
            (lambda (&rest _)
              (when-let ((messages-buffer (get-buffer "*Messages*")))
                (with-current-buffer messages-buffer
                  (evil-normalize-keymaps)))))

是呀,有时候就是这样,所以经常逛论坛、善用搜索,能解决很多问题! :laughing:

Evil-normalize-keymaps 的作用是立即激活 minor-mode 的 evil keymaps、否则需要经过一次state切换才能激活。

一般这个是常用来和 minor-mode-hook来搭配的。比如激活 eglot mode 的 evil keymap,就要把 evil-normalize-keymap 加到 eglot-managed-mode-hook 里。

另外;最好不要在 hook 里加 lambda 函数,一定要加有名字的函数,具体的原因搜一下,解释的很清楚了。

这是 issue 里 general.el 作者给的代码,建议反馈到 issue 里。