mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-05 15:18:40 +00:00
terminal/snapshot: preserve mixed-width pending wrap
SCREEN decode clamped the cursor x coordinate to the physical page width, but validated pending wrap against the terminal-wide column count. A lazily reflowed page narrower than the current terminal could therefore lose a valid pending-wrap state at its last physical column. The next write would continue on the same row instead of wrapping. Validate pending wrap against the cursor page width, matching the clamp and the page-local cursor pin. Add a mixed-width decode regression that places the cursor at the narrow page boundary.
This commit is contained in:
@@ -411,7 +411,7 @@ pub fn decode(
|
||||
.y = y,
|
||||
.cursor_style = header.cursor_style,
|
||||
.pending_wrap = header.cursor_flags.pending_wrap and
|
||||
x == options.cols - 1,
|
||||
x == row_pin.node.cols() - 1,
|
||||
.protected = header.cursor_flags.protected,
|
||||
.style = header.cursor_pen,
|
||||
.hyperlink_implicit_id = header.hyperlink_implicit_id,
|
||||
@@ -2048,6 +2048,58 @@ test "SCREEN restoration normalizes invalid cursor positions" {
|
||||
}
|
||||
}
|
||||
|
||||
test "SCREEN validates pending wrap against a mixed-width cursor page" {
|
||||
var screen = try TerminalScreen.init(
|
||||
std.testing.io,
|
||||
std.testing.allocator,
|
||||
.{ .cols = 8, .rows = 1, .max_scrollback_bytes = 0 },
|
||||
);
|
||||
defer screen.deinit();
|
||||
|
||||
// Model a lazily reflowed active page that is narrower than the terminal.
|
||||
// Column three is its physical final column even though it is not column
|
||||
// seven of the current terminal dimensions.
|
||||
var narrow_page = try terminal_page.Page.init(.{ .cols = 4, .rows = 1 });
|
||||
defer narrow_page.deinit();
|
||||
|
||||
var destination: std.Io.Writer.Allocating = .init(
|
||||
std.testing.allocator,
|
||||
);
|
||||
defer destination.deinit();
|
||||
var stream: record.Writer = .init(
|
||||
std.testing.allocator,
|
||||
&destination.writer,
|
||||
);
|
||||
defer stream.deinit();
|
||||
|
||||
var header = Header.init(&screen, .primary, 1);
|
||||
header.cursor_x = 3;
|
||||
header.cursor_y = 0;
|
||||
header.cursor_flags.pending_wrap = true;
|
||||
header.saved_cursor_present = false;
|
||||
|
||||
const screen_payload = stream.begin(.screen);
|
||||
errdefer stream.cancel();
|
||||
try header.encode(screen_payload);
|
||||
try screen_payload.writeByte(0);
|
||||
try stream.finish();
|
||||
try page.encode(&narrow_page, &stream);
|
||||
|
||||
var source: std.Io.Reader = .fixed(destination.written());
|
||||
var decoded = try decode(
|
||||
&source,
|
||||
std.testing.io,
|
||||
std.testing.allocator,
|
||||
.{ .cols = 8, .rows = 1, .max_scrollback_bytes = 0 },
|
||||
);
|
||||
defer decoded.deinit();
|
||||
|
||||
try std.testing.expectEqual(@as(u16, 4), decoded.screen.cursor.page_pin.node.cols());
|
||||
try std.testing.expectEqual(@as(u16, 3), decoded.screen.cursor.x);
|
||||
try std.testing.expect(decoded.screen.cursor.pending_wrap);
|
||||
decoded.screen.assertIntegrity();
|
||||
}
|
||||
|
||||
test "SCREEN restoration rejects invalid and incomplete sequences" {
|
||||
var screen = try TerminalScreen.init(
|
||||
std.testing.io,
|
||||
|
||||
Reference in New Issue
Block a user