terminal: handle minimum row scroll delta

PageList.scroll negated negative row deltas to obtain their
magnitude. minInt(isize) has no positive signed representation, so
callers could trigger a runtime safety panic before the existing
traversal had a chance to clamp at the top.

Use @abs to calculate an unsigned magnitude that represents every isize
value. The same value now drives both cached-pin and general traversal
paths.
This commit is contained in:
Mitchell Hashimoto
2026-07-09 20:19:37 -07:00
parent 35e1a0160c
commit c753fe4a4f

View File

@@ -2907,6 +2907,8 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
},
.delta_prompt => |n| self.scrollPrompt(n),
.delta_row => |n| delta_row: {
const amount: usize = @abs(n);
switch (self.viewport) {
// If we're at the top and we're scrolling backwards,
// we don't have to do anything, because there's nowhere to go.
@@ -2923,11 +2925,11 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
.pin => switch (std.math.order(n, 0)) {
.eq => break :delta_row,
.lt => switch (self.viewport_pin.upOverflow(@intCast(-n))) {
.lt => switch (self.viewport_pin.upOverflow(amount)) {
.offset => |new_pin| {
self.viewport_pin.* = new_pin;
if (self.viewport_pin_row_offset) |*v| {
v.* -= @as(usize, @intCast(-n));
v.* -= amount;
}
break :delta_row;
},
@@ -2939,7 +2941,7 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
},
},
.gt => switch (self.viewport_pin.downOverflow(@intCast(n))) {
.gt => switch (self.viewport_pin.downOverflow(amount)) {
// If we offset its a valid pin but we still have to
// check if we're in the active area.
.offset => |new_pin| {
@@ -2948,7 +2950,7 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
} else {
self.viewport_pin.* = new_pin;
if (self.viewport_pin_row_offset) |*v| {
v.* += @intCast(n);
v.* += amount;
}
}
break :delta_row;
@@ -2966,10 +2968,10 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
// Slow path: we have to calculate the new pin by moving
// from our viewport.
const top = self.getTopLeft(.viewport);
const p: Pin = if (n < 0) switch (top.upOverflow(@intCast(-n))) {
const p: Pin = if (n < 0) switch (top.upOverflow(amount)) {
.offset => |v| v,
.overflow => |v| v.end,
} else switch (top.downOverflow(@intCast(n))) {
} else switch (top.downOverflow(amount)) {
.offset => |v| v,
.overflow => |v| v.end,
};
@@ -8132,6 +8134,20 @@ test "PageList scroll delta row back overflow" {
}, s.scrollbar());
}
test "PageList scroll minimum row delta" {
const testing = std.testing;
var s = try init(testing.allocator, 10, 3, null);
defer s.deinit();
// Create one row of history so scrolling all the way back has an
// observable result.
try s.growRows(1);
s.scroll(.{ .delta_row = std.math.minInt(isize) });
try testing.expectEqual(Viewport.top, s.viewport);
}
test "PageList scroll delta row forward" {
const testing = std.testing;
const alloc = testing.allocator;