From b6f34be44f8efc16e58072d0681d4bb0ab076f75 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Jul 2026 21:05:51 -0700 Subject: [PATCH] terminal: clamp mirrored selection corners Rectangle orientation swaps endpoint columns when a selection is mirrored. During incomplete reflow, copying a column from a wider page onto the narrower corner page created an invalid pin that panicked when resolved. Clamp every swapped column to the page that owns the oriented corner. Top-left and bottom-right calculations now always return valid pins. --- src/terminal/Selection.zig | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index 17f6e462f..be6854e5f 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -155,12 +155,12 @@ pub fn topLeft(self: Selection, s: *const Screen) Pin { .reverse => self.end(), .mirrored_forward => pin: { var p = self.start(); - p.x = self.end().x; + p.x = @min(self.end().x, p.node.cols() - 1); break :pin p; }, .mirrored_reverse => pin: { var p = self.end(); - p.x = self.start().x; + p.x = @min(self.start().x, p.node.cols() - 1); break :pin p; }, }; @@ -173,12 +173,12 @@ pub fn bottomRight(self: Selection, s: *const Screen) Pin { .reverse => self.start(), .mirrored_forward => pin: { var p = self.end(); - p.x = self.start().x; + p.x = @min(self.start().x, p.node.cols() - 1); break :pin p; }, .mirrored_reverse => pin: { var p = self.start(); - p.x = self.end().x; + p.x = @min(self.end().x, p.node.cols() - 1); break :pin p; }, }; @@ -1055,6 +1055,32 @@ test "Selection: order, standard" { } } +test "Selection: rectangle corners clamp across mixed-width pages" { + const testing = std.testing; + var s = try Screen.init(testing.allocator, .{ + .cols = 4, + .rows = 2, + .max_scrollback = 0, + }); + defer s.deinit(); + + const first = s.pages.pages.first.?; + try s.pages.split(.{ .node = first, .y = 1 }); + const second = first.next.?; + second.page().size.cols = 2; + + const sel = Selection.init( + .{ .node = first, .x = 3 }, + .{ .node = second, .x = 1 }, + true, + ); + try testing.expectEqual(.mirrored_forward, sel.order(&s)); + + const bottom_right = sel.bottomRight(&s); + _ = bottom_right.rowAndCell(); + try testing.expect((Pin{ .node = second, .x = 1 }).eql(bottom_right)); +} + test "Selection: order, rectangle" { const testing = std.testing; const alloc = testing.allocator;