diff --git a/lib/src/core/buffer/buffer.dart b/lib/src/core/buffer/buffer.dart index 10789382..5327c868 100644 --- a/lib/src/core/buffer/buffer.dart +++ b/lib/src/core/buffer/buffer.dart @@ -235,7 +235,12 @@ class Buffer { void index() { if (isInVerticalMargin) { if (_cursorY == _marginBottom) { - if (marginTop == 0 && !isAltBuffer) { + // Growing the scrollback (insert a line just below the last visible row, so the top line is + // preserved as history) is only correct for a *full-height* scroll — a region whose bottom is + // the last row of the screen. codex/ratatui set *partial* scroll regions (e.g. `ESC[1;9r`, + // `ESC[10;35r`) to redraw part of its inline UI; scrolling one of those must shift lines *within* + // the region (`scrollUp`), not shove everything below it down — doing so scrambled the display. + if (marginTop == 0 && _marginBottom == viewHeight - 1 && !isAltBuffer) { lines.insert(absoluteMarginBottom + 1, _newEmptyLine()); } else { scrollUp(1); diff --git a/test/src/core/buffer/scroll_region_test.dart b/test/src/core/buffer/scroll_region_test.dart new file mode 100644 index 00000000..e564d8b9 --- /dev/null +++ b/test/src/core/buffer/scroll_region_test.dart @@ -0,0 +1,33 @@ +import 'package:test/test.dart'; +import 'package:xterm/core.dart'; + +void main() { + group('partial DECSTBM scroll region', () { + test('scrolling a partial region does not disturb rows below it', () { + final terminal = Terminal(); + terminal.resize(10, 6); + + // Fill six rows with identifiable content. + terminal.write('L0\r\nL1\r\nL2\r\nL3\r\nL4\r\nL5'); + + // A *partial* scroll region covering rows 1..3 (indices 0..2); the screen is 6 rows tall. + terminal.write('\x1b[1;3r'); + // Park the cursor on the bottom margin (row 3) and index — scrolls the region up by one. + terminal.write('\x1b[3;1H'); + terminal.write('\n'); + + String row(int i) => terminal.buffer.lines[i].toString().trim(); + + // Rows below the region must be untouched — the bug inserted a line below the region and shoved + // these down. + expect(row(3), 'L3'); + expect(row(4), 'L4'); + expect(row(5), 'L5'); + + // The region itself scrolled up: L0 fell off the top, L1/L2 moved up, the bottom row cleared. + expect(row(0), 'L1'); + expect(row(1), 'L2'); + expect(row(2), ''); + }); + }); +}