0

For regular buffers, Evil has a state indicator in the mode line.

But when using Evil in the minibuffer (by setting evil-want-minibuffer to t), there's no state indicator besides the cursor. Viper had this solved decades ago, by changing the face of the user's input in the minibuffer.

(Yes, the cursor is a decent indicator - it works great when you're already looking at the cursor, but it doesn't seem to work on terminals and it's not at a fixed location.)

Being able to always reliably and quickly find out Evil's state with a glance at a fixed spot in the window is really nice once you're used to it. I particularly appreciate it when I'm not sure what key I just accidentally hit, or if I look away from Emacs for a while and forget what state I'm in.

I know that I could build up the habit of just hitting ESC habitually to force a return to "normal" (command) state, but that's extra human overhead when one of them main points of a computer is that it can be a force multiplier by reliably automating things the way that is best for the user. (It also would break/preclude other ergonomics improvements - for example, I have ESC in vi command mode exit the minibuffer.)

So is there any built-in feature within Evil to do this? A third-party package? Some nice way to code that capability in my config?

1 Answer 1

0

There doesn't seem to be a built-in feature in Evil, but a decent solution using custom minibuffer faces and Evil's state hooks takes only a handful of lines.

Evil provides a set of hooks, named evil-{{state}}-(entry|exit)-hook.

The best approach I found is to just define a hook for entering every Evil state, and set the minibuffer color in each one. (Even if I didn't want to distinguish all seven states that Evil defines, I would find this pattern with some colors repeated more straightforward - easier to interpret and reason about.)

A simple implementation of that looks like this (we define a factory function, which takes a color and returns a function which will set the minibuffer foreground face to that color):

(defun color-minibuffer (color)
    `(lambda ()
        (when (minibufferp)
            (face-remap-add-relative 'minibuffer-prompt :foreground ,color))))
(add-hook 'evil-normal-state-entry-hook   (color-minibuffer "#FF0000"))
(add-hook 'evil-operator-state-entry-hook (color-minibuffer "#FF8000"))
(add-hook 'evil-insert-state-entry-hook   (color-minibuffer "#00FF00"))
(add-hook 'evil-replace-state-entry-hook  (color-minibuffer "#FFFF00"))
(add-hook 'evil-visual-state-entry-hook   (color-minibuffer "#8080FF"))
(add-hook 'evil-motion-state-entry-hook   (color-minibuffer "#A0E0FF"))
(add-hook 'evil-emacs-state-entry-hook    (color-minibuffer "#8000FF"))

Note that face-remap-add-relative is preferred to setting the minibuffer-prompt face directly, because it overrides the face per-buffer (and it turns out that the minibuffer-prompt face is reused in various places, either directly or by other faces that inherit from it, so changing it directly can have unexpected/undesired global side-effects).


Is it "safe" to steal the minibuffer color for ourselves as users as a state indicator in our config? I haven't seen any issues with it, and I remember reading somewhere that Emacs libraries are discouraged from changing the minibuffer's prompt's color precisely so that users can control it how they want.


If you're like me, you're also curious about making the indicator a character rather than a color - perhaps at the very beginning of the minibuffer text. Unfortunately, that seems to be much more involved.

Personally I currently use Vertico, so in most minibuffers, that seems to override the minibuffer prompt text repeatedly (makes sense - it has to dynamically update the number of candidates matched/total).

I think we'd have to use a minibuffer-setup-hook to setup up after-change-functions for each minibuffer which then receive every minibuffer change and have to do all of their own filtering if they want to reliably keep the indicator of the current state in the prompt.

The most robust way I can think of would probably be to use a unique text property on the indicator character(s) you inject, and then on every change, scan for those characters, and if they're not there or not in the right spot because the prompt got overwritten or pre-pended, re-edit the prompt to match.

But I am not sure if that would actually work or if you might end up with a run-away change loop where your code and some library keep fighting to re-edit the minibuffer.

More importantly, after using colors for a couple weeks, I actually think a big splotch of color is much better than a character - the difference is like hjkl vs arrow keys, once you get used to the former and try to go back, you immediately feel it. With good distinct colors, I usually don't have to move my eyes at all to mentally "query" the state, and when I do it's a much faster movement that requires far less precision in angular aim and no precision in focal distance.


Yet another super simple possibility you could build up is to use minibuffer-message, possibly wrapped with (let ((minibuffer-message-timeout some-really-large-number)) ...). I personally like the colored minibuffer prompt more, and don't like working around the message regularly timing out and disappearing from a design/architecture perspective, so I didn't explore this any further, but it's another possibility that can be used to build a very adequate Evil state indicator UX, and it might fit your preferences better.

1
  • I'll accept another answer if it's more comprehensive, or if new developments render this level of comprehensiveness obsolete.
    – mtraceur
    May 7, 2023 at 22:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.