Skip to content

Commit e4f9529

Browse files
Merge pull request #622 from OwnDing/fix-reset-scroll-area
Notify the view when a full reset replaces the buffer (blank terminal after RIS)
2 parents cf7764f + 8f91842 commit e4f9529

3 files changed

Lines changed: 58 additions & 1 deletion

File tree

Sources/SwiftTerm/Terminal.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5578,6 +5578,14 @@ open class Terminal {
55785578
cursorHidden = savedCursorHidden
55795579
refresh (startRow: 0, endRow: rows-1)
55805580
syncScrollArea ()
5581+
// A full reset replaces the buffer, so the view's scroll geometry — which
5582+
// is derived from `lines.count` and `yDisp` — is stale. `syncScrollArea()`
5583+
// is a no-op stub, and none of the other paths that recompute it fire
5584+
// here (no buffer switch, no scrolled line, no keystroke, no resize), so
5585+
// notify explicitly. Without this the view keeps the contentSize and
5586+
// contentOffset of a buffer that no longer exists and renders blank until
5587+
// some unrelated layout pass happens to correct it.
5588+
tdel?.bufferActivated (source: self)
55815589
}
55825590

55835591
// Support for:
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Testing
2+
@testable import SwiftTerm
3+
4+
/// A full reset (RIS, `ESC c`) replaces the buffer, so any view that derives its
5+
/// scroll geometry from `lines.count` / `yDisp` has to be told about it.
6+
/// `syncScrollArea()` is a no-op stub, and none of the other paths that make a
7+
/// view recompute that geometry run here — there is no buffer switch, no scrolled
8+
/// line, no keystroke and no resize — so `resetToInitialState()` notifies
9+
/// explicitly. Without that, a view keeps the contentSize/contentOffset of the
10+
/// buffer that was just discarded and renders blank.
11+
@Suite("Full reset scroll area")
12+
struct ResetScrollAreaTests {
13+
@Test("RIS notifies the delegate that the buffer changed")
14+
func fullResetNotifiesDelegate() {
15+
let (terminal, delegate) = TerminalTestHarness.makeTerminal(cols: 80, rows: 24, scrollback: 500)
16+
17+
// Build up scrollback so the reset is a real change in buffer size.
18+
for i in 0..<200 {
19+
terminal.feed(text: "line \(i)\r\n")
20+
}
21+
#expect(terminal.buffer.lines.count > terminal.rows)
22+
23+
let before = delegate.bufferActivatedCount
24+
terminal.feed(text: "\u{1b}c")
25+
26+
// The buffer really did shrink back to a single screen...
27+
#expect(terminal.buffer.lines.count == terminal.rows)
28+
#expect(terminal.buffer.yBase == 0)
29+
#expect(terminal.buffer.yDisp == 0)
30+
// ...and the delegate heard about it exactly once.
31+
#expect(delegate.bufferActivatedCount == before + 1)
32+
}
33+
34+
@Test("Keypad mode changes do not claim the buffer changed")
35+
func keypadModeDoesNotNotify() {
36+
let (terminal, delegate) = TerminalTestHarness.makeTerminal(cols: 80, rows: 24, scrollback: 500)
37+
38+
let before = delegate.bufferActivatedCount
39+
terminal.feed(text: "\u{1b}=") // application keypad
40+
terminal.feed(text: "\u{1b}>") // numeric keypad
41+
42+
// These also call syncScrollArea(), but they do not touch the buffer, so
43+
// they must not be reported as a buffer activation: on iOS that resets
44+
// the scroll-follow state and would yank a user who has scrolled back
45+
// down to the bottom.
46+
#expect(delegate.bufferActivatedCount == before)
47+
}
48+
}

Tests/SwiftTermTests/TerminalTestHarness.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Testing
33

44
final class TerminalTestDelegate: TerminalDelegate {
55
private(set) var sentData: [[UInt8]] = []
6+
private(set) var bufferActivatedCount = 0
67
var cellSizeInPixelsValue: (width: Int, height: Int)? = nil
78

89
func showCursor(source: Terminal) {}
@@ -13,7 +14,7 @@ final class TerminalTestDelegate: TerminalDelegate {
1314
func sizeChanged(source: Terminal) {}
1415
func scrolled(source: Terminal, yDisp: Int) {}
1516
func linefeed(source: Terminal) {}
16-
func bufferActivated(source: Terminal) {}
17+
func bufferActivated(source: Terminal) { bufferActivatedCount += 1 }
1718
func bell(source: Terminal) {}
1819

1920
func send(source: Terminal, data: ArraySlice<UInt8>) {

0 commit comments

Comments
 (0)