aboutsummaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-04 16:32:43 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-04 16:32:43 +0300
commit648afa1fd5b4c2eb74b4292b4eceeeb6d93016c1 (patch)
tree368bce0f173319b98bce0e99f0c829fcd54520b3 /vendor
parentf5f753c7286ff4cc30cfe3a9d48c053bfe2881c7 (diff)
fix: backport libghostty resize overflow fix
refs #465
Diffstat (limited to 'vendor')
-rw-r--r--vendor/libghostty-vt.patches.md40
-rw-r--r--vendor/libghostty-vt/src/terminal/PageList.zig35
-rw-r--r--vendor/libghostty-vt/src/terminal/c/terminal.zig23
-rw-r--r--vendor/patches/libghostty-vt/0001-backport-resizecols-cursor-subtraction.patch115
4 files changed, 211 insertions, 2 deletions
diff --git a/vendor/libghostty-vt.patches.md b/vendor/libghostty-vt.patches.md
new file mode 100644
index 00000000..98b7569e
--- /dev/null
+++ b/vendor/libghostty-vt.patches.md
@@ -0,0 +1,40 @@
+# libghostty-vt local patches
+
+This file tracks intentional local changes applied on top of the vendored
+`libghostty-vt` source. Remove a patch only when the vendored source commit
+contains the upstream fix and the listed verification still passes.
+
+## 0001 backport resizeCols cursor subtraction saturation
+
+status: active
+
+patch: `vendor/patches/libghostty-vt/0001-backport-resizecols-cursor-subtraction.patch`
+
+herdr issue: https://github.com/ogulcancelik/herdr/issues/465
+
+upstream discussion: https://github.com/ghostty-org/ghostty/discussions/12905
+
+upstream pr: https://github.com/ghostty-org/ghostty/pull/12907
+
+introduced upstream: `c44afa625`
+
+vendored base: `0f7cd84b880b203c98683e520e84b9db0c5938d8`
+
+local files:
+
+- `vendor/libghostty-vt/src/terminal/PageList.zig`
+- `vendor/libghostty-vt/src/terminal/c/terminal.zig`
+
+reason: shrinking rows and columns in one resize can leave the pre-resize
+cursor row past the new row count. `PageList.resizeCols` then computed rows
+below the cursor with checked unsigned subtraction and aborted in safety builds.
+
+remove when: the vendored source commit contains upstream PR #12907 and the
+local ReleaseSafe resize regression tests pass without this patch.
+
+verification:
+
+```sh
+zig build test-lib-vt -Demit-lib-vt -Doptimize=ReleaseSafe -Dtest-filter="resize shrinks both axes with cursor at bottom"
+zig build test-lib-vt -Demit-lib-vt -Doptimize=ReleaseSafe -Dtest-filter="PageList resize less rows and cols cursor at bottom"
+```
diff --git a/vendor/libghostty-vt/src/terminal/PageList.zig b/vendor/libghostty-vt/src/terminal/PageList.zig
index 8e5cd193..63155962 100644
--- a/vendor/libghostty-vt/src/terminal/PageList.zig
+++ b/vendor/libghostty-vt/src/terminal/PageList.zig
@@ -1059,7 +1059,7 @@ fn resizeCols(
break :cursor .{
.tracked_pin = c.pin orelse try self.trackPin(p),
.untrack = c.pin == null,
- .remaining_rows = self.rows - c.y - 1,
+ .remaining_rows = self.rows -| (c.y + 1),
.wrapped_rows = wrapped,
};
} else null;
@@ -1192,7 +1192,7 @@ fn resizeCols(
break :wrapped wrapped;
};
- const current = self.rows - active_pt.active.y - 1;
+ const current = self.rows -| (active_pt.active.y + 1);
var req_rows = c.remaining_rows;
req_rows -|= wrapped -| c.wrapped_rows;
@@ -10828,6 +10828,37 @@ test "PageList resize (no reflow) less rows and cols" {
}
}
+test "PageList resize less rows and cols cursor at bottom" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, 0);
+ defer s.deinit();
+
+ const cursor_pin = try s.trackPin(s.pin(.{ .active = .{
+ .x = 0,
+ .y = s.rows - 1,
+ } }).?);
+ defer s.untrackPin(cursor_pin);
+
+ // Shrink both axes so the original cursor.y is strictly past the new row
+ // count, after resizeWithoutReflow has already reduced self.rows.
+ try s.resize(.{
+ .cols = 79,
+ .rows = 20,
+ .reflow = true,
+ .cursor = .{ .x = 0, .y = 23, .pin = cursor_pin },
+ });
+ try testing.expectEqual(@as(usize, 79), s.cols);
+ try testing.expectEqual(@as(usize, 20), s.rows);
+
+ // remaining_rows saturates to 0, so the cursor lands on the new bottom row.
+ try testing.expectEqual(point.Point{ .active = .{
+ .x = 0,
+ .y = s.rows - 1,
+ } }, s.pointFromPin(.active, cursor_pin.*).?);
+}
+
test "PageList resize (no reflow) more rows and less cols" {
const testing = std.testing;
const alloc = testing.allocator;
diff --git a/vendor/libghostty-vt/src/terminal/c/terminal.zig b/vendor/libghostty-vt/src/terminal/c/terminal.zig
index 5a5db2d6..39959f83 100644
--- a/vendor/libghostty-vt/src/terminal/c/terminal.zig
+++ b/vendor/libghostty-vt/src/terminal/c/terminal.zig
@@ -1005,6 +1005,29 @@ test "resize invalid value" {
try testing.expectEqual(Result.invalid_value, resize(t, 80, 0, 9, 18));
}
+test "resize shrinks both axes with cursor at bottom" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ // CSI 24;1H parks the cursor on the bottom row.
+ const move = "\x1b[24;1H";
+ vt_write(t, move, move.len);
+
+ // Shrink both axes; pre-resize cursor.y sits past the new bottom row.
+ try testing.expectEqual(Result.success, resize(t, 79, 23, 8, 16));
+ try testing.expectEqual(79, t.?.terminal.cols);
+ try testing.expectEqual(23, t.?.terminal.rows);
+}
+
test "mode_get and mode_set" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
diff --git a/vendor/patches/libghostty-vt/0001-backport-resizecols-cursor-subtraction.patch b/vendor/patches/libghostty-vt/0001-backport-resizecols-cursor-subtraction.patch
new file mode 100644
index 00000000..1f8d0f36
--- /dev/null
+++ b/vendor/patches/libghostty-vt/0001-backport-resizecols-cursor-subtraction.patch
@@ -0,0 +1,115 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: herdr maintainers <maintainers@herdr.dev>
+Date: Thu, 4 Jun 2026 00:00:00 +0000
+Subject: [PATCH] backport resizeCols cursor subtraction saturation
+
+Backport ghostty-org/ghostty#12907 onto the vendored libghostty-vt source.
+Shrink-both resize can leave the old cursor row past the new row count after
+resizeWithoutReflow reduces self.rows. Saturating subtraction treats rows below
+that cursor as zero instead of trapping in safety builds.
+
+Herdr issue: https://github.com/ogulcancelik/herdr/issues/465
+Upstream discussion: https://github.com/ghostty-org/ghostty/discussions/12905
+Upstream PR: https://github.com/ghostty-org/ghostty/pull/12907
+Vendored base: 0f7cd84b880b203c98683e520e84b9db0c5938d8
+---
+ vendor/libghostty-vt/src/terminal/PageList.zig | 33 +++++++++++++++++++++--
+ vendor/libghostty-vt/src/terminal/c/terminal.zig | 23 ++++++++++++++++
+ 2 files changed, 54 insertions(+), 2 deletions(-)
+
+diff --git a/vendor/libghostty-vt/src/terminal/PageList.zig b/vendor/libghostty-vt/src/terminal/PageList.zig
+index 0000000..0000000 100644
+--- a/vendor/libghostty-vt/src/terminal/PageList.zig
++++ b/vendor/libghostty-vt/src/terminal/PageList.zig
+@@ -1059,7 +1059,7 @@ fn resizeCols(
+ break :cursor .{
+ .tracked_pin = c.pin orelse try self.trackPin(p),
+ .untrack = c.pin == null,
+- .remaining_rows = self.rows - c.y - 1,
++ .remaining_rows = self.rows -| (c.y + 1),
+ .wrapped_rows = wrapped,
+ };
+ } else null;
+@@ -1192,7 +1192,7 @@ fn resizeCols(
+ break :wrapped wrapped;
+ };
+
+- const current = self.rows - active_pt.active.y - 1;
++ const current = self.rows -| (active_pt.active.y + 1);
+
+ var req_rows = c.remaining_rows;
+ req_rows -|= wrapped -| c.wrapped_rows;
+@@ -10828,6 +10828,37 @@ test "PageList resize (no reflow) less rows and cols" {
+ }
+ }
+
++test "PageList resize less rows and cols cursor at bottom" {
++ const testing = std.testing;
++ const alloc = testing.allocator;
++
++ var s = try init(alloc, 80, 24, 0);
++ defer s.deinit();
++
++ const cursor_pin = try s.trackPin(s.pin(.{ .active = .{
++ .x = 0,
++ .y = s.rows - 1,
++ } }).?);
++ defer s.untrackPin(cursor_pin);
++
++ // Shrink both axes so the original cursor.y is strictly past the new row
++ // count, after resizeWithoutReflow has already reduced self.rows.
++ try s.resize(.{
++ .cols = 79,
++ .rows = 20,
++ .reflow = true,
++ .cursor = .{ .x = 0, .y = 23, .pin = cursor_pin },
++ });
++ try testing.expectEqual(@as(usize, 79), s.cols);
++ try testing.expectEqual(@as(usize, 20), s.rows);
++
++ // remaining_rows saturates to 0, so the cursor lands on the new bottom row.
++ try testing.expectEqual(point.Point{ .active = .{
++ .x = 0,
++ .y = s.rows - 1,
++ } }, s.pointFromPin(.active, cursor_pin.*).?);
++}
++
+ test "PageList resize (no reflow) more rows and less cols" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+diff --git a/vendor/libghostty-vt/src/terminal/c/terminal.zig b/vendor/libghostty-vt/src/terminal/c/terminal.zig
+index 0000000..0000000 100644
+--- a/vendor/libghostty-vt/src/terminal/c/terminal.zig
++++ b/vendor/libghostty-vt/src/terminal/c/terminal.zig
+@@ -1005,6 +1005,29 @@ test "resize invalid value" {
+ try testing.expectEqual(Result.invalid_value, resize(t, 80, 0, 9, 18));
+ }
+
++test "resize shrinks both axes with cursor at bottom" {
++ var t: Terminal = null;
++ try testing.expectEqual(Result.success, new(
++ &lib.alloc.test_allocator,
++ &t,
++ .{
++ .cols = 80,
++ .rows = 24,
++ .max_scrollback = 0,
++ },
++ ));
++ defer free(t);
++
++ // CSI 24;1H parks the cursor on the bottom row.
++ const move = "\x1b[24;1H";
++ vt_write(t, move, move.len);
++
++ // Shrink both axes; pre-resize cursor.y sits past the new bottom row.
++ try testing.expectEqual(Result.success, resize(t, 79, 23, 8, 16));
++ try testing.expectEqual(79, t.?.terminal.cols);
++ try testing.expectEqual(23, t.?.terminal.rows);
++}
++
+ test "mode_get and mode_set" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+--
+2.49.0