Skip to content

feat: expose userScrolling for scroll-lock during output#530

Open
k3rnelpan11c wants to merge 1 commit into
migueldeicaza:mainfrom
k3rnelpan11c:flock-scroll-lock
Open

feat: expose userScrolling for scroll-lock during output#530
k3rnelpan11c wants to merge 1 commit into
migueldeicaza:mainfrom
k3rnelpan11c:flock-scroll-lock

Conversation

@k3rnelpan11c

Copy link
Copy Markdown

Summary

  • Make Terminal.userScrolling public so host apps can set it when the user scrolls away from the bottom
  • Guard selection clearing in feedPrepare() and linefeed() with the userScrolling flag so active selections persist while new output streams in
  • scrollWheel changed from public to open in MacTerminalView to allow subclass override

The userScrolling flag already exists and is checked in Terminal.scroll() (lines 5245, 5255, 5281) — these changes simply wire it up for external consumers.

Motivation

When using terminal-based AI tools (like Claude Code) that produce long streaming output, users cannot:

  1. Scroll up to read earlier output without being snapped back to the bottom
  2. Select text without the selection being cleared by incoming data
  3. Maintain a selection while new content arrives

These 3 minimal changes (~6 lines) fix all three issues by leveraging the existing userScrolling infrastructure.

Test plan

  • Verify default behavior unchanged (userScrolling defaults to false)
  • Set terminal.userScrolling = true and verify scroll position is preserved during feed()
  • Set terminal.userScrolling = true and verify text selection persists through feed()
  • Verify scrollWheel can be overridden in subclass

🤖 Generated with Claude Code

…output

Make `Terminal.userScrolling` public so host applications can set it when
the user has scrolled away from the bottom. Guard selection clearing in
`feedPrepare()` and `linefeed()` with the flag so that active selections
are preserved while new output streams in.

The flag already exists and is checked in `Terminal.scroll()` — these
changes simply wire it up for external consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
k3rnelpan11c pushed a commit to k3rnelpan11c/flock that referenced this pull request Apr 7, 2026
Track user scroll state in FlockTerminalView and set
terminal.userScrolling to prevent auto-scroll-to-bottom and selection
clearing while the user is reading history or has text selected.

- Override scrollWheel to detect when user scrolls away from bottom
- Set userScrolling flag before data feed to preserve scroll position
- Protect active selections from being cleared by incoming output
- Release scroll lock when user sends input (typing returns to bottom)

Depends on k3rnelpan11c/SwiftTerm@flock-scroll-lock (PR migueldeicaza/SwiftTerm#530)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@migueldeicaza

Copy link
Copy Markdown
Owner

Did you see the comments on #468?

@k3rnelpan11c

Copy link
Copy Markdown
Author

Thanks for the pointer to #468! I've reviewed your concerns there and here's how this PR addresses them:

1. yDisp synchronization — In our approach, userScrolling is set by the host app (Flock) from within the scrollWheel override, right after super.scrollWheel() has already updated yDisp. So the flag and yDisp stay in sync — we're not toggling the flag independently of scroll state.

2. Buffer switching — Good point. When the alternate screen buffer activates (vim, less, etc.), canScroll returns false and scrollPosition returns 0, so the host app's scrollWheel override naturally sets userScrolling = false. But we should verify this edge case more thoroughly.

3. Delegate semantics — Unlike #468, we don't hijack the delegate at all. The only change is making userScrolling public so the host app can set it. The existing feedPrepare() and linefeed() selection-clearing logic just gets an additional guard (&& !terminal.userScrolling). No delegate behavior changes.

4. Scroll thresholdscrollWheel fires on any scroll input, so there's no minimum threshold. The check is scrollPosition >= 0.999 to detect "at bottom".

5. Keyboard reset — Addressed: the host app resets userScrolling = false in the send() override when the user types.

The changes are minimal (~6 lines in SwiftTerm) and all the coordination logic lives in the host app. Would you prefer we address anything else?

AlbertoNoris added a commit to AlbertoNoris/SwiftTerm that referenced this pull request Apr 9, 2026
Two minimal changes so iOS hosts can implement sticky-bottom scroll
behavior while streaming output is arriving.

- Terminal.swift: expose the existing `userScrolling` flag as public.
  The flag already gates `yDisp` advancement inside `Terminal.scroll()`,
  but was previously unreachable from outside the module. Matches the
  intent of upstream PR migueldeicaza#530.

- iOS/iOSTerminalView.swift: `updateScroller()` now leaves
  `contentOffset` alone when `terminal.userScrolling` is true. On iOS
  this function is called from many paths (scrolled, synchronizedOutput
  begin/end, bufferActivated, sizeChanged, etc.), so gating the snap in
  one place covers all of them. `contentSize` still updates so the
  scroll view knows the new buffer extents.

Upstream behavior is unchanged when userScrolling is false (the
default), so this is a strict superset of stock SwiftTerm.

Nepsis ships this fork to work around the viewport snap-to-bottom that
Claude Code's CSI ?2026h/l synchronized-output stream triggers on every
token frame.
getnashty added a commit to fliptables/SwiftTerm that referenced this pull request Apr 14, 2026
Make Terminal.userScrolling public so host apps (like Scape) can set
it from their scrollWheel override to suppress auto-scroll when the
user has scrolled up during streaming output.

Changes:
- Terminal.userScrolling: internal → public
- MacTerminalView.scrollWheel: public → open (allows subclass override)
- scroll(toPosition:): set terminal.userScrolling based on resulting
  position instead of using dead shadow variable
- feedPrepare(): guard selection clearing with !terminal.userScrolling
  to preserve selection during scroll-lock
- Remove dead MacTerminalView.userScrolling shadow variable

Inspired by SwiftTerm PR migueldeicaza#530 (host-app coordination approach).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getnashty added a commit to fliptables/SwiftTerm that referenced this pull request Apr 14, 2026
Make Terminal.userScrolling public so host apps (like Scape) can set
it from their scrollWheel override to suppress auto-scroll when the
user has scrolled up during streaming output.

Changes:
- Terminal.userScrolling: internal → public
- MacTerminalView.scrollWheel: public → open (allows subclass override)
- scroll(toPosition:): set terminal.userScrolling based on resulting
  position instead of using dead shadow variable
- feedPrepare(): guard selection clearing with !terminal.userScrolling
  to preserve selection during scroll-lock
- Remove dead MacTerminalView.userScrolling shadow variable

Inspired by SwiftTerm PR migueldeicaza#530 (host-app coordination approach).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@migueldeicaza

Copy link
Copy Markdown
Owner

Oh, I had not seen this update, will review.

@migueldeicaza

Copy link
Copy Markdown
Owner

Would you mind testing this branch, and rebasing the above patch on this? prevent-scroll-while-dragging

getnashty added a commit to fliptables/SwiftTerm that referenced this pull request May 19, 2026
Make Terminal.userScrolling public so host apps (like Scape) can set
it from their scrollWheel override to suppress auto-scroll when the
user has scrolled up during streaming output.

Changes:
- Terminal.userScrolling: internal → public
- MacTerminalView.scrollWheel: public → open (allows subclass override)
- scroll(toPosition:): set terminal.userScrolling based on resulting
  position instead of using dead shadow variable
- feedPrepare(): guard selection clearing with !terminal.userScrolling
  to preserve selection during scroll-lock
- Remove dead MacTerminalView.userScrolling shadow variable

Inspired by SwiftTerm PR migueldeicaza#530 (host-app coordination approach).

Co-Authored-By: Claude Opus 4.6 (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.

2 participants