Search code examples
emacscursorevil-mode

Change cursor to insert state while in minubuffer


I change my cursor based on state in evil. However I notice that when I type in the minibuffer I'm in normal mode.

I'm trying to make code that change the state from whatever it's in to insert state while I type in the minibuffer and switch back when I exit the minibuffer. Note, I use ivy for minibuffer completion (though I don't think it makes a difference).

(setq evil-insert-state-cursor '((bar . 3) "chartreuse3"))
(defun void-ivy-insert-state (orig-func &rest args)
   "Wrapper around ivy, so it goes into insert state."

   ;; minibuffer is different so I have to manually change the state
   (let ((saved-evil-state evil-state))
     (evil-insert-state) 
     (setq cursor-type (elt evil-insert-state-cursor 0))
     (set-cursor-color (elt evil-insert-state-cursor 1))
     (apply orig-func args)
     (evil-change-state saved-evil-state)))

(advice-add 'ivy-read :around #'void-ivy-insert-state)

I expect the cursor to be the right color and the write shape while typing something into the minibuffer. And to return to the appropriate shape of the state I was in before entering the minibuffer.

What actually happens is that the cursor is the right color, but it's the wrong shape. And if I exit the minibuffer with C-g insert state persists. It never returns to the original state. I think C-g aborts the execution of my advice.


Solution

  • I achieved the desired behavior with this code.

    (add-hook 'minibuffer-setup-hook (lambda () (evil-insert-state)))
    (add-hook 'minibuffer-exit-hook (lambda () (evil-normal-state)))
    (define-key evil-insert-state-map [escape] (lambda () (interactive)
                                                          (evil-normal-state)
                                                          (minibuffer-keyboard-quit)))
    

    The only annoying side effect is that whenever ESC is pressed No recursive edit is in progress is printed to the output.

    The version below is the one I use in my code. Instead of returning to normal state, it uses a variable to return to the previous evil-state before the minibuffer was opened. Also it only uses minibuffer-keyboard-quit if the minibuffer is active.

    (defvar evil-state-before-minibuffer-setup 'normal)
    
    (add-hook 'minibuffer-setup-hook
          (lambda () (setq evil-state-before-minibuffer-setup evil-state) 
                     (evil-insert-state)))
    
    (add-hook 'minibuffer-exit-hook (lambda () 
                  (evil-change-state evil-state-before-minibuffer-setup)))
    
    (define-key evil-insert-state-map [escape] 
          (lambda () (interactive) (evil-normal-state) 
                                   (when (eq (active-minibuffer-window))
                                             (selected-window))
                                            (minibuffer-keyboard-quit))))
    

    I make sure I only quit when the minibuffer is the selected window. Otherwise, the minibuffer would quit if we were to, say, edit a buffer while viewing the contents of the minibuffer.