fix(default): restore evil-paste-pop in comint

evil-collection overrides C-p/C-n in comint-derived buffers (shell,
REPLs), preventing evil-paste-pop from cycling the kill ring after a
paste. This adds a conditional dispatch that checks `last-command' to
choose between paste-pop and comint history navigation.

Fix: #3602

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kovan 2026-02-26 15:30:50 +01:00
parent 85ccc61ddc
commit bcde438b71
2 changed files with 76 additions and 0 deletions

View file

@ -118,6 +118,25 @@
(:after geiser-doc :map geiser-doc-mode-map
:n "o" #'link-hint-open-link)
;; HACK Restore evil-paste-pop in comint-derived modes (shell, REPLs,
;; etc), where evil-collection overrides C-p/C-n in normal state. This
;; checks `last-command' to dispatch between paste-pop and the original
;; comint history navigation commands.
;; REVIEW: PR this upstream!
(:after comint :map comint-mode-map
:n "C-p" (cmd! (if (memq last-command '(evil-paste-after
evil-paste-before
evil-paste-pop
evil-paste-pop-next))
(call-interactively #'evil-paste-pop)
(comint-previous-input 1)))
:n "C-n" (cmd! (if (memq last-command '(evil-paste-after
evil-paste-before
evil-paste-pop
evil-paste-pop-next))
(call-interactively #'evil-paste-pop-next)
(comint-next-input 1))))
(:unless (modulep! :input layout +bepo)
(:after (evil-org evil-easymotion)
:map evil-org-mode-map

View file

@ -0,0 +1,57 @@
;; -*- no-byte-compile: t; -*-
;;; config/default/test/test-evil-bindings.el
(require 'buttercup)
(require 'evil)
(require 'comint)
(describe "config/default/+evil-bindings"
(describe "comint C-p/C-n dispatch"
(before-each
(with-current-buffer (get-buffer-create "*test-comint*")
(comint-mode)
(evil-local-mode +1)
(evil-normal-state)))
(after-each
(when (get-buffer "*test-comint*")
(kill-buffer "*test-comint*")))
(it "dispatches C-p to comint-previous-input after non-paste commands"
(with-current-buffer "*test-comint*"
(let ((last-command 'evil-next-line))
(expect (memq last-command
'(evil-paste-after evil-paste-before
evil-paste-pop evil-paste-pop-next))
:to-be nil))))
(it "dispatches C-p to evil-paste-pop after evil-paste-after"
(with-current-buffer "*test-comint*"
(let ((last-command 'evil-paste-after))
(expect (memq last-command
'(evil-paste-after evil-paste-before
evil-paste-pop evil-paste-pop-next))
:to-be-truthy))))
(it "dispatches C-p to evil-paste-pop after evil-paste-before"
(with-current-buffer "*test-comint*"
(let ((last-command 'evil-paste-before))
(expect (memq last-command
'(evil-paste-after evil-paste-before
evil-paste-pop evil-paste-pop-next))
:to-be-truthy))))
(it "dispatches C-p to evil-paste-pop after evil-paste-pop"
(with-current-buffer "*test-comint*"
(let ((last-command 'evil-paste-pop))
(expect (memq last-command
'(evil-paste-after evil-paste-before
evil-paste-pop evil-paste-pop-next))
:to-be-truthy))))
(it "binds C-p in normal state to a conditional dispatch (not raw comint-previous-input)"
(with-current-buffer "*test-comint*"
(evil-normal-state)
(let ((binding (key-binding (kbd "C-p"))))
(expect binding :not :to-equal 'comint-previous-input)
(expect binding :not :to-equal 'evil-paste-pop))))))