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.