feat: expose userScrolling for scroll-lock during output#530
feat: expose userScrolling for scroll-lock during output#530k3rnelpan11c wants to merge 1 commit into
Conversation
…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>
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>
|
Did you see the comments on #468? |
|
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, 2. Buffer switching — Good point. When the alternate screen buffer activates (vim, less, etc.), 3. Delegate semantics — Unlike #468, we don't hijack the delegate at all. The only change is making 4. Scroll threshold — 5. Keyboard reset — Addressed: the host app resets 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? |
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.
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>
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>
|
Oh, I had not seen this update, will review. |
|
Would you mind testing this branch, and rebasing the above patch on this? prevent-scroll-while-dragging |
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>
Summary
Terminal.userScrollingpublic so host apps can set it when the user scrolls away from the bottomfeedPrepare()andlinefeed()with theuserScrollingflag so active selections persist while new output streams inscrollWheelchanged frompublictoopeninMacTerminalViewto allow subclass overrideThe
userScrollingflag already exists and is checked inTerminal.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:
These 3 minimal changes (~6 lines) fix all three issues by leveraging the existing
userScrollinginfrastructure.Test plan
userScrollingdefaults tofalse)terminal.userScrolling = trueand verify scroll position is preserved duringfeed()terminal.userScrolling = trueand verify text selection persists throughfeed()scrollWheelcan be overridden in subclass🤖 Generated with Claude Code