mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-05 15:18:40 +00:00
terminal/snapshot: clamp decoded saved cursors
SCREEN decoding restored saved cursor coordinates directly from the wire even when they exceeded the current terminal dimensions, unlike the live cursor restoration path.
This commit is contained in:
@@ -435,7 +435,7 @@ pub fn decode(
|
||||
options.max_scrollback_bytes == 0,
|
||||
.cursor = cursor,
|
||||
.saved_cursor = if (saved_cursor) |value|
|
||||
value.terminal()
|
||||
value.terminal(options.cols, options.rows)
|
||||
else
|
||||
null,
|
||||
.charset = header.charset,
|
||||
@@ -813,13 +813,18 @@ pub const SavedCursor = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn terminal(self: SavedCursor) TerminalScreen.SavedCursor {
|
||||
fn terminal(
|
||||
self: SavedCursor,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
) TerminalScreen.SavedCursor {
|
||||
const x = @min(self.x, cols - 1);
|
||||
return .{
|
||||
.x = self.x,
|
||||
.y = self.y,
|
||||
.x = x,
|
||||
.y = @min(self.y, rows - 1),
|
||||
.style = self.pen,
|
||||
.protected = self.flags.protected,
|
||||
.pending_wrap = self.flags.pending_wrap,
|
||||
.pending_wrap = self.flags.pending_wrap and x == cols - 1,
|
||||
.origin = self.flags.origin,
|
||||
.charset = self.charset,
|
||||
};
|
||||
@@ -1784,8 +1789,8 @@ test "framed native SCREEN and PAGE sequence" {
|
||||
}
|
||||
|
||||
const restored_saved = restored.saved_cursor.?;
|
||||
try std.testing.expectEqual(@as(u16, 0x0102), restored_saved.x);
|
||||
try std.testing.expectEqual(@as(u16, 0x0304), restored_saved.y);
|
||||
try std.testing.expectEqual(@as(u16, 7), restored_saved.x);
|
||||
try std.testing.expectEqual(@as(u16, 7), restored_saved.y);
|
||||
try std.testing.expect(restored_saved.protected);
|
||||
try std.testing.expect(restored_saved.pending_wrap);
|
||||
try std.testing.expect(restored_saved.origin);
|
||||
@@ -2158,6 +2163,57 @@ test "SCREEN validates pending wrap against a mixed-width cursor page" {
|
||||
decoded.screen.assertIntegrity();
|
||||
}
|
||||
|
||||
test "SCREEN clamps a decoded saved cursor to terminal dimensions" {
|
||||
var screen = try TerminalScreen.init(
|
||||
std.testing.io,
|
||||
std.testing.allocator,
|
||||
.{ .cols = 2, .rows = 2, .max_scrollback_bytes = 0 },
|
||||
);
|
||||
defer screen.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.saved_cursor_present = true;
|
||||
const screen_payload = stream.begin(.screen);
|
||||
try header.encode(screen_payload);
|
||||
try (SavedCursor{
|
||||
.x = std.math.maxInt(u16),
|
||||
.y = std.math.maxInt(u16),
|
||||
.pen = .{},
|
||||
.flags = .{ .pending_wrap = true },
|
||||
.charset = .{},
|
||||
}).encode(screen_payload);
|
||||
try screen_payload.writeByte(0);
|
||||
try stream.finish();
|
||||
try page.encode(
|
||||
screen.pages.getTopLeft(.active).node.pageAssumeResident(),
|
||||
&stream,
|
||||
);
|
||||
|
||||
var source: std.Io.Reader = .fixed(destination.written());
|
||||
var decoded = try decode(
|
||||
&source,
|
||||
std.testing.io,
|
||||
std.testing.allocator,
|
||||
.{ .cols = 2, .rows = 2, .max_scrollback_bytes = 0 },
|
||||
);
|
||||
defer decoded.deinit();
|
||||
|
||||
const saved = decoded.screen.saved_cursor.?;
|
||||
try std.testing.expectEqual(@as(u16, 1), saved.x);
|
||||
try std.testing.expectEqual(@as(u16, 1), saved.y);
|
||||
try std.testing.expect(saved.pending_wrap);
|
||||
}
|
||||
|
||||
test "SCREEN restoration rejects invalid and incomplete sequences" {
|
||||
var screen = try TerminalScreen.init(
|
||||
std.testing.io,
|
||||
|
||||
Reference in New Issue
Block a user