Skip to content

Clamp window-point when TUI cursor lands at point-max (fix #138)#139

Merged
dakra merged 1 commit intomainfrom
fix-138-pending-wrap-anchor
Apr 19, 2026
Merged

Clamp window-point when TUI cursor lands at point-max (fix #138)#139
dakra merged 1 commit intomainfrom
fix-138-pending-wrap-anchor

Conversation

@dakra
Copy link
Copy Markdown
Owner

@dakra dakra commented Apr 19, 2026

Summary

  • Fixes ghostel--anchor-window + scroll-conservatively: cursor-past-last-row shifts viewport up by one row #138: when a TUI's cursor ends up in the pending-wrap state on the last visible row, ghostel--anchor-window receives pt == point-max == window-end. Emacs redisplay then classifies point as off-screen and — because ghostel-mode sets scroll-conservatively=101 — shifts window-start up by a row to keep point visible, which fights the viewport pin and makes the block cursor disappear until the next focus event.
  • In ghostel--anchor-window, clamp the value passed to set-window-point to pt-1 when pt == point-max (and the buffer is non-empty). Buffer (point) is untouched; the next redraw recaptures the real cursor.
  • Adds ERT regression test ghostel-test-anchor-window-clamps-pending-wrap covering the clamp, non-clamp, empty-buffer, and trailing-newline cases.

Test plan

  • make lint (byte-compile + checkdoc + package-lint)
  • make -j4 all — 126/126 elisp tests + 78/78 native tests pass
  • New ERT test passes
  • Live smoke via emacs --batch: with pt=point-max, window-point clamps to pt-1; with pt<point-max, unchanged
  • Manual verification in the issue-reporter's scenario (Claude Code TUI, focus-out → repaint): block cursor no longer disappears, viewport no longer shifts up a row

When a TUI's cursor is in pending-wrap state on the last visible row,
the native redraw can set (point) to point-max (one past the last
character). Emacs redisplay then treats that position as off-screen
and — with ghostel-mode's scroll-conservatively=101 — shifts
window-start up by a row to make it visible. This fights the viewport
pin in ghostel--anchor-window and makes the block cursor disappear
until the next focus event.

Clamp window-point back by one in ghostel--anchor-window when pt equals
point-max so it sits inside the viewport. Buffer-point is untouched,
and subsequent redraws recapture the real cursor.

Closes #138.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a TUI cursor “pending-wrap at point-max” edge case where Emacs redisplay (with scroll-conservatively=101) scrolls the window by one row, breaking Ghostel’s viewport pin and causing the block cursor to disappear.

Changes:

  • Clamp the value passed to set-window-point in ghostel--anchor-window when pt == point-max (non-empty buffer) to keep the rendered point inside the viewport.
  • Add an ERT regression test covering clamp/non-clamp behavior plus empty-buffer and trailing-newline cases.
  • Register the new test in the pure-Elisp test list.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
ghostel.el Adds point-max pending-wrap clamp in ghostel--anchor-window to prevent redisplay from auto-scrolling against the viewport anchor.
test/ghostel-test.el Adds and registers an ERT regression test to ensure clamping behavior across key edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ghostel.el
@dakra dakra merged commit 17fc791 into main Apr 19, 2026
26 checks passed
dakra added a commit that referenced this pull request Apr 19, 2026
PR #139 clamped `window-point` to pt-1 whenever pt equalled point-max
to fix the TUI pending-wrap scenario from #138.  That condition also
matches the normal case of a user typing at a shell prompt, where the
cursor legitimately sits at point-max after the last character.  The
block cursor was drawn on top of the last character instead of after
it, which is very visible over SSH (no shell-integration OSC between
keystrokes to nudge point off point-max).

Only clamp when libghostty reports the active cursor in pending-wrap
state.  Expose that flag via a new `ghostel--cursor-pending-wrap-p'
native function, backed by GHOSTTY_TERMINAL_DATA_CURSOR_PENDING_WRAP.

Test changes:
- Rename the elisp regression to
  `ghostel-test-anchor-window-no-clamp-without-pending-wrap' and assert
  the #146 contract (no term -> no clamp even at point-max).
- Add native `ghostel-test-cursor-pending-wrap-p' covering the flag.
- Add native `ghostel-test-anchor-window-clamps-on-pending-wrap'
  exercising both branches through a real terminal.

Closes #146.
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.

ghostel--anchor-window + scroll-conservatively: cursor-past-last-row shifts viewport up by one row

2 participants