Skip to content

Suppress cursor when cell face background matches cursor color#307

Closed
devsunb wants to merge 1 commit into
dakra:mainfrom
devsunb:suppress-cursor-on-bg-match
Closed

Suppress cursor when cell face background matches cursor color#307
devsunb wants to merge 1 commit into
dakra:mainfrom
devsunb:suppress-cursor-on-bg-match

Conversation

@devsunb
Copy link
Copy Markdown
Contributor

@devsunb devsunb commented May 21, 2026

Summary

Hide the Emacs cursor when its color collides with the cursor cell's face background, so terminal apps that draw their own reverse-video cursor (Claude Code, Gemini CLI, etc.) stay visible.

Why

Emacs's GUI ports (X11, mac-port, w32, pgtk) share a set_cursor_gc distinct-swap fallback that swaps the cursor's fg/bg with the cell's fg/bg whenever they match (xterm.c:8017 and the mac-port/w32/pgtk equivalents). It's meant to keep the cursor visible against a matching cell, but it backfires for apps that draw their input cursor via SGR reverse-video: on a reverse-video cell the swap reproduces the colors of the surrounding non-inverse cells, so the Emacs box cursor becomes visually indistinguishable from normal text.

Fix

Detect the collision and force cursor-type off so the cell's own reverse-video face represents the cursor:

(defun ghostel--cursor-cell-conflicts-p ()
  (when-let* ((pos ghostel--cursor-char-pos)
              ((< pos (point-max)))
              (cell-bg (ghostel--cell-face-background pos))
              (cursor-color (frame-parameter nil 'cursor-color))
              (cell-rgb (color-values cell-bg))
              (cursor-rgb (color-values cursor-color)))
    (equal cell-rgb cursor-rgb)))

Suppression runs from a buffer-local pre-redisplay-functions hook rather than from ghostel--set-cursor-style. A one-time assignment there would not stick, since cursor-type is restored to its default between module render passes; the hook reapplies the suppression just before each redisplay. It also keeps the cursor off when the terminal sends DECTCEM hide (\e[?25l), tracked in ghostel--cursor-visible.

Screenshots

Before

before before2

After

after after2

A terminal app that draws its input cursor with SGR reverse-video
(e.g. Claude Code) leaves the Emacs cursor visually fused with the
cell beneath it.  Emacs's GUI ports swap the cursor's fg/bg with the
cell's fg/bg when cursor-color matches the cell background — see
`set_cursor_gc' in xterm.c:8017 and the mac-port/w32/pgtk
equivalents — and on a reverse-video cell, that swap reproduces the
colors of the surrounding non-inverse cells.

Detect the collision and force `cursor-type' to nil so the cell's
own inverse video face represents the cursor.  The check runs from a
buffer-local `pre-redisplay-functions' hook rather than from
`ghostel--set-cursor-style': a one-time assignment there would not
stick, because cursor-type is restored to its default between module
render passes, so the hook reapplies the suppression just before each
redisplay.

Track the DECTCEM hide state (`\e[?25l') in `ghostel--cursor-visible'
along the way so the hook also keeps the cursor off when a TUI menu
hides it.
@dakra
Copy link
Copy Markdown
Owner

dakra commented May 21, 2026

@devsunb Thanks. Can you open an issue with more info and exact steps how to reproduce it?

Neither claude nor gemini have any problems here. I set my focused cursor color exactly the same as the default fg, but everything works as expected.

Also I'm not sure if a pre-redisplay-function that always runs and hides the cursor is the best solution.
So I would prefer if I can first reproduce the issue.

@devsunb
Copy link
Copy Markdown
Contributor Author

devsunb commented May 22, 2026

I found the root cause. The cursor was being redrawn by evil-mode, not by anything in this package. Sorry for the noise.

For future reference, I had been disabling evil in ghostel buffers like this:

(with-eval-after-load 'evil
  (evil-set-initial-state 'ghostel-mode 'emacs))

But emacs state still keeps evil active, so the cursor was being redrawn. The fix was to exclude the buffer from evil entirely:

(with-eval-after-load 'evil
  (add-to-list 'evil-buffer-regexps '("\\`\\*ghostel" . nil)))

Thanks for the patient review and for creating this package.

@devsunb devsunb closed this May 22, 2026
@devsunb devsunb deleted the suppress-cursor-on-bg-match branch May 22, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants