diff --git a/Sources/SwiftTerm/Apple/AppleTerminalView.swift b/Sources/SwiftTerm/Apple/AppleTerminalView.swift index 2cfbda2f3..890cd71b4 100644 --- a/Sources/SwiftTerm/Apple/AppleTerminalView.swift +++ b/Sources/SwiftTerm/Apple/AppleTerminalView.swift @@ -1927,10 +1927,14 @@ extension TerminalView { let displayBuffer = terminal.displayBuffer if row != displayBuffer.yDisp { terminal.setViewYDisp (row) - + + // Track whether the user has scrolled away from the bottom so that + // incoming output does not yank the viewport back down. + terminal.userScrolling = displayBuffer.yDisp != displayBuffer.yBase + // tell the terminal we want to refresh all the rows terminal.refresh (startRow: 0, endRow: terminal.rows) - + // do the display update updateDisplay (notifyAccessibility: notifyAccessibility) //selectionView.notifyScrolled(source: terminal) @@ -1978,8 +1982,10 @@ extension TerminalView { func feedPrepare() { search.invalidate() - // Preserve manual selection while output is streaming when mouse reporting is disabled. - if allowMouseReporting { + // Only clear selection when the hosted app has actually requested mouse + // reporting — otherwise the user should be able to select text while + // output is streaming. + if allowMouseReporting && terminal.mouseMode != .off { selection.active = false } startDisplayUpdates() diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index 93eac10cf..ffe931920 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -762,8 +762,10 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, } open func linefeed(source: Terminal) { - // Preserve manual selection while output is streaming when mouse reporting is disabled. - if allowMouseReporting { + // Only clear selection when the hosted app has actually requested mouse + // reporting — otherwise the user should be able to select text while + // output is streaming. + if allowMouseReporting && terminal.mouseMode != .off { selection.selectNone() } } diff --git a/Sources/SwiftTerm/iOS/iOSTerminalView.swift b/Sources/SwiftTerm/iOS/iOSTerminalView.swift index ccc2048d1..92821a25e 100644 --- a/Sources/SwiftTerm/iOS/iOSTerminalView.swift +++ b/Sources/SwiftTerm/iOS/iOSTerminalView.swift @@ -1418,8 +1418,10 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi } open func linefeed(source: Terminal) { - // Preserve manual selection while output is streaming when mouse reporting is disabled. - if allowMouseReporting { + // Only clear selection when the hosted app has actually requested mouse + // reporting — otherwise the user should be able to select text while + // output is streaming. + if allowMouseReporting && terminal.mouseMode != .off { selection.selectNone() disableSelectionPanGesture() } @@ -1430,10 +1432,10 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi let displayBuffer = terminal.displayBuffer contentSize = CGSize (width: CGFloat (displayBuffer.cols) * cellDimension.width, height: CGFloat (displayBuffer.lines.count) * cellDimension.height) - //contentOffset = CGPoint (x: 0, y: CGFloat (displayBuffer.lines.count-displayBuffer.rows)*cellDimension.height) - contentOffset = CGPoint (x: 0, y: CGFloat (displayBuffer.lines.count-displayBuffer.rows)*cellDimension.height) - //Xscroller.doubleValue = scrollPosition - //Xscroller.knobProportion = scrollThumbsize + // Only scroll to bottom if the user hasn't explicitly scrolled up + if !terminal.userScrolling { + contentOffset = CGPoint (x: 0, y: CGFloat (displayBuffer.lines.count-displayBuffer.rows)*cellDimension.height) + } } #if canImport(MetalKit) @@ -1541,6 +1543,15 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi requestMetalDisplay() } #endif + // When the user is actively scrolling (dragging or decelerating), + // track whether they have scrolled away from the bottom so incoming + // output does not yank the viewport back down. + if isDragging || isDecelerating { + let displayBuffer = terminal.displayBuffer + let bottomOffset = CGFloat(displayBuffer.lines.count - displayBuffer.rows) * cellDimension.height + let atBottom = contentOffset.y >= bottomOffset - 1 + terminal.userScrolling = !atBottom + } } } @@ -2178,6 +2189,7 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi guard !terminal.synchronizedOutputActive else { return } let displayBuffer = terminal.displayBuffer contentOffset = CGPoint (x: 0, y: CGFloat (displayBuffer.lines.count-displayBuffer.rows)*cellDimension.height) + terminal.userScrolling = false } public func deleteBackward() {