diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 3f9dadd43..fa0c04352 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -2734,7 +2734,7 @@ pub fn resize(self: *Screen, rows: usize, cols: usize) !void { // Convert our cursor coordinates to screen coordinates because // we may have to reflow the cursor if the line it is on is unwrapped. - const cursor_pos = (point.Viewport{ + const cursor_pos = (point.Active{ .x = old.cursor.x, .y = old.cursor.y, }).toScreen(&old); @@ -2948,7 +2948,7 @@ pub fn resize(self: *Screen, rows: usize, cols: usize) !void { // Convert our cursor coordinates to screen coordinates because // we may have to reflow the cursor if the line it is on is moved. - const cursor_pos = (point.Viewport{ + const cursor_pos = (point.Active{ .x = old.cursor.x, .y = old.cursor.y, }).toScreen(&old); @@ -6372,6 +6372,72 @@ test "Screen: resize more cols perfect split" { try s.resize(3, 10); } +// https://github.com/mitchellh/ghostty/issues/1159 +test "Screen: resize (no reflow) more cols with scrollback scrolled up" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 5); + defer s.deinit(); + const str = "1\n2\n3\n4\n5\n6\n7\n8"; + try s.testWriteString(str); + + // Cursor at bottom + try testing.expectEqual(@as(usize, 1), s.cursor.x); + try testing.expectEqual(@as(usize, 2), s.cursor.y); + + try s.scroll(.{ .viewport = -4 }); + { + const contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings("2\n3\n4", contents); + } + + try s.resize(3, 8); + { + const contents = try s.testString(alloc, .screen); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } + + // Cursor remains at bottom + try testing.expectEqual(@as(usize, 1), s.cursor.x); + try testing.expectEqual(@as(usize, 2), s.cursor.y); +} + +// https://github.com/mitchellh/ghostty/issues/1159 +test "Screen: resize (no reflow) less cols with scrollback scrolled up" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 5); + defer s.deinit(); + const str = "1\n2\n3\n4\n5\n6\n7\n8"; + try s.testWriteString(str); + + // Cursor at bottom + try testing.expectEqual(@as(usize, 1), s.cursor.x); + try testing.expectEqual(@as(usize, 2), s.cursor.y); + + try s.scroll(.{ .viewport = -4 }); + { + const contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings("2\n3\n4", contents); + } + + try s.resize(3, 4); + { + const contents = try s.testString(alloc, .screen); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } + + // Cursor remains at bottom + try testing.expectEqual(@as(usize, 1), s.cursor.x); + try testing.expectEqual(@as(usize, 2), s.cursor.y); +} + test "Screen: resize more cols no reflow preserves semantic prompt" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/point.zig b/src/terminal/point.zig index fc248cbda..8c694f992 100644 --- a/src/terminal/point.zig +++ b/src/terminal/point.zig @@ -6,6 +6,34 @@ const Screen = terminal.Screen; // use different types so that we can lean on type-safety to get the // exact expected type of point. +/// Active is a point within the active part of the screen. +pub const Active = struct { + x: usize = 0, + y: usize = 0, + + pub fn toScreen(self: Active, screen: *const Screen) ScreenPoint { + return .{ + .x = self.x, + .y = screen.history + self.y, + }; + } + + test "toScreen with scrollback" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try Screen.init(alloc, 3, 5, 3); + defer s.deinit(); + const str = "1\n2\n3\n4\n5\n6\n7\n8"; + try s.testWriteString(str); + + try testing.expectEqual(ScreenPoint{ + .x = 1, + .y = 5, + }, (Active{ .x = 1, .y = 2 }).toScreen(&s)); + } +}; + /// Viewport is a point within the viewport of the screen. pub const Viewport = struct { x: usize = 0,