0

In evil-mode, the cursor shape and colour for each state is controlled by the series of variables evil-*-state-cursor. The documentation for these variables states:

May be a cursor type as per cursor-type, a color string as passed to set-cursor-color, a zero-argument function for changing the cursor, or a list of the above.

However, when I attempt to use such a nullary function to make the shape of evil-insert-state-cursor dependent on the value of overwrite-mode, it doesn't work:

(setq evil-insert-state-cursor
      (lambda()
        (cond (overwrite-mode '("chartreuse3" (hbar . 2)))
              (t              '("chartreuse3" (bar  . 2))))))

The result of this is that the insert cursor changes to a hollow box in the same colour as the normal state cursor (Darkgoldenrod2 on my system). If I use a named function instead, I get a solid box (ie. identical to the normal state cursor).

I've also attempted to use a function that actually sets the value of evil-insert-state-cursor, rather than simply returning it. The result was the same. In general, setting the variable to any function results in a solid cursor, while setting it to any symbol/quoted sexp or lambda results in a hollow cursor.

How can I set a state-cursor variable to a function effectively?

(I've found the answer to this before posting, but since I've already written the question, I might as well post it with the answer in case anyone else is searching for it.)

1

So I've found the answer to this is evil-states.el. Here's an example of a cursor defined with a function:

(evil-define-state operator
  "Operator-Pending state."
  :tag " <O> "
  :cursor evil-half-cursor
  :enable (evil-operator-shortcut-map operator motion normal))

...and the function definition:

;; the half-height "Operator-Pending cursor" cannot be specified
;; as a static `cursor-type' value, since its height depends on
;; the current font size
(defun evil-half-cursor ()
  "Change cursor to a half-height box.
\(This is really just a thick horizontal bar.)"
  (let ((height (/ (window-pixel-height) (* (window-height) 2))))
    (setq cursor-type (cons 'hbar height))))

As you can see, the function must set the cursor-type variable with the appropriate value.

This example is a bit different from my use-case, since the cursor doesn't need to change after the state is entered. Reasonably enough, evil doesn't re-evaluate the function every keystroke, so for a use-case like mine, we also need to invoke evil-refresh-cursor to do as its name implies.

Additionally, this ignores cursor-colour, so the cursor will have whatever colour it had last (probably from normal-state). We can fix this by also calling set-cursor-color in our function.

Solution:

(defun my/evil-insert-overwrite-cursor ()
  (set-cursor-color "chartreuse3")
  (setq cursor-type (if overwrite-mode (cons 'hbar 2) (cons 'bar 2))))

(setq evil-insert-state-cursor #'my/evil-insert-overwrite-cursor)

(defun my/enter-overwrite-mode ()
  (interactive)
  (call-interactively #'overwrite-mode)
  (evil-refresh-cursor))

(define-key evil-insert-state-map (kbd "<insert>") 'my/enter-overwrite-mode)

note: There is an overwrite-mode-hook, so we could add evil-refresh-cursor to that, to be sure it will always be triggered. However, I only expect to enter overwrite-mode with Insert and I prefer to use wrapper functions unless hooks are necessary.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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