Skip to content

Deactivate a selection when terminal output repaints it - #705

Merged
dakra merged 1 commit into
mainfrom
fix/selection-repaint
Sep 18, 2026
Merged

dakra merged 1 commit into
mainfrom
fix/selection-repaint

Conversation

@dakra

@dakra dakra commented Sep 18, 2026

Copy link
Copy Markdown
Owner

A selection left active over terminal output kept its buffer positions
while the app rewrote the cells under them, so it ended up highlighting
text that was never selected. The reported case is an alternate-screen
program redrawing its whole grid after a window resize (e.g. a minibuffer
growing and shrinking the other windows).

This is xterm's keepSelection: false behaviour: output that touches the
selected rows drops the selection.

The change

The renderer already publishes the buffer range it rewrote in
ghostel--repainted-region. ghostel--deactivate-repainted-region
deactivates the region when that range overlaps it, with
select-active-regions bound off so the replaced text does not reach the
primary selection. No new state, no Zig change; it costs 54 ns on a
redraw with no region active, the second-cheapest of the eight per-redraw
calls.

Carve-outs:

  • An empty region is spared. It highlights nothing, and dropping it would
    also resume window following, throwing away a scrolled-up view.
  • Line mode is exempt: it passes FULL on every redraw, so its repainted
    range is always the whole buffer. A TUI taking the alternate screen
    pauses line mode to semi-char, so alt-screen selections stay covered.
  • Only a redraw that actually painted is tested.
  • A signalling deactivate-mark-hook is demoted so it cannot skip the
    link scan, the line-mode resume and the password check that follow.

Who sees a change

Only ghostel-mouse-drag-input-mode set to emacs or nil. The default,
copy mode, pauses redraws, so nothing can repaint under a selection
there. Under a timer-refreshing TUI (htop and friends) a selection in the
live modes now dies at the next refresh rather than lying about what it
covers; copy mode remains the way to hold one.

Testing

make -j8 all green. Four new tests: three elisp (overlap with
select-active-regions off, a boundary table of six geometries, line
mode spared, a signalling hook contained) and one native end-to-end that
drives the reported path — alt screen, select a row, resize, app repaints
through ghostel--filter. The native one fails when the new function is
stubbed out.

Verified live in a sandboxed Emacs against htop: the stale-selection
symptom reproduces on demand with the fix neutralised, and normal-screen
selections in scrollback are untouched by new shell output.

Known gaps

  • Evil visual state draws its selection with evil's own overlay, so
    deactivate-mark leaves the highlight in place. Mouse selection changes after resizing an alternate-screen Ghostel window #704 is not fixed for
    evil users; that belongs in evil-ghostel.
  • The published range is the union of the flushed spans, so a repaint of
    two distant rows can drop a selection sitting on untouched rows between
    them. Accepted imprecision; a per-span range would mean publishing a
    list from the renderer.

Fixes #704

@dakra
dakra force-pushed the fix/selection-repaint branch from 12b6d66 to 99f257d Compare September 18, 2026 20:38
Terminal output replaces text in place, so a selection left active ends
up highlighting text the user never selected: the region keeps its
buffer positions while the app rewrites the cells under them.  An
alternate-screen program redrawing its whole grid after a window resize
is the common case.

The renderer already publishes the buffer range it rewrote in
`ghostel--repainted-region'.  Deactivate the region when that range
overlaps it, with `select-active-regions' bound off so the replaced text
does not reach the primary selection.

An empty region has nothing to lose and is spared, which also keeps a
bare mark from resuming window following.  Line mode repaints the whole
buffer on every redraw and is exempt; a TUI taking the alternate screen
pauses line mode to semi-char, so alt-screen selections stay covered.
Only a redraw that painted is tested, and a signalling
`deactivate-mark-hook' is demoted so it cannot skip the rest of the
redraw cycle.

Copy mode, the default for a mouse drag, pauses redraws and is
unaffected; `emacs' and nil keep drawing, so a selection there dies at
the next repaint of its rows.  Evil's visual state is drawn with evil's
own overlay and is untouched by this.

Fixes #704
@dakra
dakra force-pushed the fix/selection-repaint branch from 99f257d to 6be5648 Compare September 18, 2026 20:46
@dakra
dakra merged commit 6be5648 into main Sep 18, 2026
33 checks passed
@dakra
dakra deleted the fix/selection-repaint branch September 18, 2026 22:14

This branch was successfully deployed

1 active deployment
github-pages — 6be56484 Deployed Sep 18, 2026 by dakra via deploy #201
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.

Mouse selection changes after resizing an alternate-screen Ghostel window

1 participant