From cc7c696cd0772e466da403eb4e3f00f4e8d8ae1e Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sat, 19 Sep 2026 14:47:18 +0300 Subject: feat: add configurable pane screen and scrollback clearing (#4383) --- vendor/libghostty-vt.patches.md | 38 ++++++++++ vendor/libghostty-vt/include/ghostty/vt/terminal.h | 8 ++ vendor/libghostty-vt/src/lib_vt.zig | 1 + vendor/libghostty-vt/src/terminal/c/main.zig | 1 + vendor/libghostty-vt/src/terminal/c/terminal.zig | 34 +++++++++ .../0006-clear-screen-preserving-cursor-line.patch | 88 ++++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 vendor/patches/libghostty-vt/0006-clear-screen-preserving-cursor-line.patch (limited to 'vendor') diff --git a/vendor/libghostty-vt.patches.md b/vendor/libghostty-vt.patches.md index 10596292..cd7f44f6 100644 --- a/vendor/libghostty-vt.patches.md +++ b/vendor/libghostty-vt.patches.md @@ -132,3 +132,41 @@ just test-one link_activation just test-one ctrl_click just check ``` + +## 0006 clear screen while preserving the cursor line + +status: active + +patch: `vendor/patches/libghostty-vt/0006-clear-screen-preserving-cursor-line.patch` + +herdr issue: none; requested in https://github.com/herdrdev/herdr/discussions/545 + +upstream discussion: not opened + +upstream pr: not opened + +vendored base: `44f2a44df7e8c4a0c6df3f7d872ef3d7ead88e51` + +local files: + +- `vendor/libghostty-vt/include/ghostty/vt/terminal.h` +- `vendor/libghostty-vt/src/lib_vt.zig` +- `vendor/libghostty-vt/src/terminal/c/main.zig` +- `vendor/libghostty-vt/src/terminal/c/terminal.zig` + +reason: Herdr needs an explicit screen/history clear that preserves the cursor's +visible soft-wrapped line without writing to the child or interrupting a partial +VT sequence. The new C function operates directly on the screen, leaves alternate +screens untouched, clears image placements, and marks the result dirty. + +remove when: the vendored C API provides an equivalent parser-independent clear +operation preserving the visible cursor line, and Herdr passes the checks below +using it without this patch. + +verification: + +```sh +just test-one clear_pane +just maintenance-test +just check +``` diff --git a/vendor/libghostty-vt/include/ghostty/vt/terminal.h b/vendor/libghostty-vt/include/ghostty/vt/terminal.h index ad5eec54..81f9ebd0 100644 --- a/vendor/libghostty-vt/include/ghostty/vt/terminal.h +++ b/vendor/libghostty-vt/include/ghostty/vt/terminal.h @@ -2007,6 +2007,14 @@ GHOSTTY_API void ghostty_terminal_free(GhosttyTerminal terminal); */ GHOSTTY_API void ghostty_terminal_reset(GhosttyTerminal terminal); +/** + * Clear screen and history, retaining the cursor's soft-wrapped active line. + * Does not alter the VT parser or write to the child process. Returns false + * without changing the terminal on the alternate screen or for a NULL handle. + * Otherwise returns true and moves the retained line to the top of the screen. + */ +GHOSTTY_API bool ghostty_terminal_clear_screen(GhosttyTerminal terminal); + /** * Resize the terminal to the given dimensions. * diff --git a/vendor/libghostty-vt/src/lib_vt.zig b/vendor/libghostty-vt/src/lib_vt.zig index 48536568..05bc0baa 100644 --- a/vendor/libghostty-vt/src/lib_vt.zig +++ b/vendor/libghostty-vt/src/lib_vt.zig @@ -317,6 +317,7 @@ comptime { @export(&c.terminal_new, .{ .name = "ghostty_terminal_new" }); @export(&c.terminal_free, .{ .name = "ghostty_terminal_free" }); @export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" }); + @export(&c.terminal_clear_screen, .{ .name = "ghostty_terminal_clear_screen" }); @export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" }); @export(&c.terminal_set, .{ .name = "ghostty_terminal_set" }); @export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" }); diff --git a/vendor/libghostty-vt/src/terminal/c/main.zig b/vendor/libghostty-vt/src/terminal/c/main.zig index 0ba1224a..5ac299ab 100644 --- a/vendor/libghostty-vt/src/terminal/c/main.zig +++ b/vendor/libghostty-vt/src/terminal/c/main.zig @@ -183,6 +183,7 @@ pub const sys_set = sys.set; pub const terminal_new = terminal.new; pub const terminal_free = terminal.free; pub const terminal_reset = terminal.reset; +pub const terminal_clear_screen = terminal.clearScreen; pub const terminal_resize = terminal.resize; pub const terminal_set = terminal.set; pub const terminal_vt_write = terminal.vt_write; diff --git a/vendor/libghostty-vt/src/terminal/c/terminal.zig b/vendor/libghostty-vt/src/terminal/c/terminal.zig index 73097573..91bbe310 100644 --- a/vendor/libghostty-vt/src/terminal/c/terminal.zig +++ b/vendor/libghostty-vt/src/terminal/c/terminal.zig @@ -1504,6 +1504,40 @@ pub fn reset(terminal_: Terminal) callconv(lib.calling_conv) void { t.fullReset(); } +/// Clear output without feeding synthetic bytes into the child's VT stream. +pub fn clearScreen(terminal_: Terminal) callconv(lib.calling_conv) bool { + const t: *ZigTerminal = (terminal_ orelse return false).terminal; + if (t.screens.active_key == .alternate) return false; + const screen = t.screens.active; + + var first = screen.cursor.y; + while (first > 0) { + const pin = screen.pages.pin(.{ .active = .{ .y = first } }).?; + if (!pin.rowAndCell().row.wrap_continuation) break; + first -= 1; + } + var last = screen.cursor.y; + while (last + 1 < t.rows) { + const pin = screen.pages.pin(.{ .active = .{ .y = last } }).?; + if (!pin.rowAndCell().row.wrap) break; + last += 1; + } + + screen.clearSelection(); + if (last + 1 < t.rows) { + screen.clearRows(.{ .active = .{ .y = last + 1 } }, null, false); + } + screen.eraseHistory(null); + if (first > 0) screen.eraseActive(first - 1); + screen.pages.pin(.{ .active = .{} }).?.rowAndCell().row.wrap_continuation = false; + screen.scroll(.active); + if (comptime build_options.kitty_graphics) { + screen.kitty_images.delete(t.io(), screen.alloc, t, .{ .all = true }); + } + t.flags.dirty.clear = true; + return true; +} + /// C: GhosttyKittyGraphics pub const KittyGraphics = kitty_gfx_c.KittyGraphics; diff --git a/vendor/patches/libghostty-vt/0006-clear-screen-preserving-cursor-line.patch b/vendor/patches/libghostty-vt/0006-clear-screen-preserving-cursor-line.patch new file mode 100644 index 00000000..86fcee9a --- /dev/null +++ b/vendor/patches/libghostty-vt/0006-clear-screen-preserving-cursor-line.patch @@ -0,0 +1,88 @@ +diff --git a/vendor/libghostty-vt/include/ghostty/vt/terminal.h b/vendor/libghostty-vt/include/ghostty/vt/terminal.h +index ad5eec54..81f9ebd0 100644 +--- a/vendor/libghostty-vt/include/ghostty/vt/terminal.h ++++ b/vendor/libghostty-vt/include/ghostty/vt/terminal.h +@@ -2007,6 +2007,14 @@ GHOSTTY_API void ghostty_terminal_free(GhosttyTerminal terminal); + */ + GHOSTTY_API void ghostty_terminal_reset(GhosttyTerminal terminal); + ++/** ++ * Clear screen and history, retaining the cursor's soft-wrapped active line. ++ * Does not alter the VT parser or write to the child process. Returns false ++ * without changing the terminal on the alternate screen or for a NULL handle. ++ * Otherwise returns true and moves the retained line to the top of the screen. ++ */ ++GHOSTTY_API bool ghostty_terminal_clear_screen(GhosttyTerminal terminal); ++ + /** + * Resize the terminal to the given dimensions. + * +diff --git a/vendor/libghostty-vt/src/lib_vt.zig b/vendor/libghostty-vt/src/lib_vt.zig +index 48536568..05bc0baa 100644 +--- a/vendor/libghostty-vt/src/lib_vt.zig ++++ b/vendor/libghostty-vt/src/lib_vt.zig +@@ -317,6 +317,7 @@ comptime { + @export(&c.terminal_new, .{ .name = "ghostty_terminal_new" }); + @export(&c.terminal_free, .{ .name = "ghostty_terminal_free" }); + @export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" }); ++ @export(&c.terminal_clear_screen, .{ .name = "ghostty_terminal_clear_screen" }); + @export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" }); + @export(&c.terminal_set, .{ .name = "ghostty_terminal_set" }); + @export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" }); +diff --git a/vendor/libghostty-vt/src/terminal/c/main.zig b/vendor/libghostty-vt/src/terminal/c/main.zig +index 0ba1224a..5ac299ab 100644 +--- a/vendor/libghostty-vt/src/terminal/c/main.zig ++++ b/vendor/libghostty-vt/src/terminal/c/main.zig +@@ -183,6 +183,7 @@ pub const sys_set = sys.set; + pub const terminal_new = terminal.new; + pub const terminal_free = terminal.free; + pub const terminal_reset = terminal.reset; ++pub const terminal_clear_screen = terminal.clearScreen; + pub const terminal_resize = terminal.resize; + pub const terminal_set = terminal.set; + pub const terminal_vt_write = terminal.vt_write; +diff --git a/vendor/libghostty-vt/src/terminal/c/terminal.zig b/vendor/libghostty-vt/src/terminal/c/terminal.zig +index 73097573..91bbe310 100644 +--- a/vendor/libghostty-vt/src/terminal/c/terminal.zig ++++ b/vendor/libghostty-vt/src/terminal/c/terminal.zig +@@ -1504,6 +1504,40 @@ pub fn reset(terminal_: Terminal) callconv(lib.calling_conv) void { + t.fullReset(); + } + ++/// Clear output without feeding synthetic bytes into the child's VT stream. ++pub fn clearScreen(terminal_: Terminal) callconv(lib.calling_conv) bool { ++ const t: *ZigTerminal = (terminal_ orelse return false).terminal; ++ if (t.screens.active_key == .alternate) return false; ++ const screen = t.screens.active; ++ ++ var first = screen.cursor.y; ++ while (first > 0) { ++ const pin = screen.pages.pin(.{ .active = .{ .y = first } }).?; ++ if (!pin.rowAndCell().row.wrap_continuation) break; ++ first -= 1; ++ } ++ var last = screen.cursor.y; ++ while (last + 1 < t.rows) { ++ const pin = screen.pages.pin(.{ .active = .{ .y = last } }).?; ++ if (!pin.rowAndCell().row.wrap) break; ++ last += 1; ++ } ++ ++ screen.clearSelection(); ++ if (last + 1 < t.rows) { ++ screen.clearRows(.{ .active = .{ .y = last + 1 } }, null, false); ++ } ++ screen.eraseHistory(null); ++ if (first > 0) screen.eraseActive(first - 1); ++ screen.pages.pin(.{ .active = .{} }).?.rowAndCell().row.wrap_continuation = false; ++ screen.scroll(.active); ++ if (comptime build_options.kitty_graphics) { ++ screen.kitty_images.delete(t.io(), screen.alloc, t, .{ .all = true }); ++ } ++ t.flags.dirty.clear = true; ++ return true; ++} ++ + /// C: GhosttyKittyGraphics + pub const KittyGraphics = kitty_gfx_c.KittyGraphics; + -- cgit v1.2.3