Skip to content

Keep point with the prompt across streamed fragment inserts#13

Draft
timvisher-dd wants to merge 1 commit into
mainfrom
timvisher/fix-streaming-cursor-drop
Draft

Keep point with the prompt across streamed fragment inserts#13
timvisher-dd wants to merge 1 commit into
mainfrom
timvisher/fix-streaming-cursor-drop

Conversation

@timvisher-dd

Copy link
Copy Markdown
Owner

An old bug came back: while the agent streams, the cursor jumps up and gets stranded above the freshly inserted text instead of staying with the prompt. It reproduces across machines and is intermittent, which is what makes it annoying to pin down.

Problem

When point is sitting right at the prompt — an empty prompt, or point moved to the start of pending input — a streamed message fragment can land above point, leaving the cursor behind the new text with the prompt pushed below it. It only happens when the buffer is not auto-scrolling at that instant (you've scrolled up a touch, or window-end is momentarily trailing point-max while data flows), so it feels random.

Cause

The shell render path (agent-shell--update-fragment / agent-shell--update-text) leans on shell-maker-with-auto-scroll-edit's save-excursion to hold point across the insertion. In the non-auto-scroll branch that's a plain save-excursion, whose marker is insertion-type nil. New fragments are inserted at the insert-cursor (the process-mark). When point happens to sit exactly at that position, inserting there leaves an insertion-type nil marker before the inserted text — so point is restored above the new fragment.

The tool-call output path (agent-shell--append-tool-call-output) never had this problem because it already saves point as (copy-marker (point) t) — an insertion-type t marker advances past insertions made at point. The message/fragment path was simply never given the same treatment.

This is a regression of the class fixed in 6babf80 ("Preserve point and window-start across streaming rerenders"), which was superseded by the insert-cursor streaming rework in 25a3b75 (PR #7). Neither shipped a test that put point at the insert position, so nothing failed when the architecture changed underneath it.

Fix

Add agent-shell--with-preserved-point, a small macro that:

  • saves point in an insertion-type t marker, so it advances past text inserted at point rather than being left behind it, and
  • snaps to point-max when point was at end-of-buffer — i.e. the prompt — so a streaming chunk never leaves the cursor behind regardless of shell-maker's auto-scroll decision.

Both shell-branch call sites (agent-shell--update-fragment and agent-shell--update-text) are wrapped in it, outside agent-shell--with-preserved-process-mark so the point restore is the final word. This mirrors the already-immune tool-output path rather than reaching back into shell-maker's save-excursion.

Tests

Two ERT regression tests in tests/agent-shell-streaming-tests.el:

  • agent-shell--with-preserved-point-keeps-cursor-at-empty-prompt-test — point at an empty prompt (end-of-buffer); a fragment inserted there must leave point at point-max, not stranded behind it.
  • agent-shell--with-preserved-point-advances-past-insertion-test — point at the start of pending input but not at end-of-buffer; point must advance past the inserted text, staying with the input.

Both were verified to fail against the old insertion-type nil save-excursion behavior and pass with the fix, so they actually guard the regression rather than just exercising the happy path. Full bin/test is green (246 tests, 0 unexpected, 1 expected skip). README features list updated.

Checklist

  • I agree to communicate (PR description and comments) with the author myself (not AI-generated).
  • I've reviewed all code in PR myself and will vouch for its quality.
  • I've read and followed the Contributing guidelines.
  • I've filed a feature request/discussion for a new feature. N/A — bug fix (tracked in private bd).
  • I'm making visual changes, so I'm including screenshots so you can view and discuss. N/A — cursor-position behavior; no static screenshot captures it.
  • I've added tests where applicable.
  • I've updated documentation where necessary.
  • I've run M-x checkdoc and M-x byte-compile-file.

The shell render path (agent-shell--update-fragment/--update-text)
leaned on shell-maker-with-auto-scroll-edit's save-excursion to
preserve point. That marker is insertion-type nil, so when the
insert-cursor sits exactly at point — an empty prompt, or point at the
start of pending input — and auto-scroll is suppressed (window-end
trails point-max while data streams), text inserted at the marker
leaves it behind. Point ends up above the freshly inserted fragment
and the prompt drops below it.

Add agent-shell--with-preserved-point, which saves point in an
insertion-type t marker (so it advances past insertions made at point)
and snaps to point-max when point was at end-of-buffer. Wrap both
shell-branch call sites. This mirrors agent-shell--append-tool-call-output,
which was already immune for the same reason.

Regression of the class fixed by 6babf80, re-exposed by the
insert-cursor streaming rework in 25a3b75 — neither shipped a test
covering point at the insert position, so add two.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant