From bdb566068ead29409eb5d410249f02f51d274d3d Mon Sep 17 00:00:00 2001 From: Vishal Kapur Date: Wed, 27 May 2026 11:14:12 -0700 Subject: [PATCH 1/2] Expose semantic prompt state through C APIs --- include/ghostty.h | 1 + include/ghostty/vt/terminal.h | 11 +++++++++++ src/Surface.zig | 11 +++++++++++ src/apprt/embedded.zig | 5 +++++ src/terminal/c/terminal.zig | 28 ++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index 5523654c4..efcd2901e 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -1134,6 +1134,7 @@ GHOSTTY_API ghostty_app_t ghostty_surface_app(ghostty_surface_t); GHOSTTY_API ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t, ghostty_surface_context_e); GHOSTTY_API void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t); GHOSTTY_API bool ghostty_surface_needs_confirm_quit(ghostty_surface_t); +GHOSTTY_API bool ghostty_surface_is_at_prompt(ghostty_surface_t); GHOSTTY_API bool ghostty_surface_process_exited(ghostty_surface_t); GHOSTTY_API void ghostty_surface_refresh(ghostty_surface_t); GHOSTTY_API void ghostty_surface_draw(ghostty_surface_t); diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 31cd5c4f1..57d0e5d03 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -1613,6 +1613,17 @@ typedef enum GHOSTTY_ENUM_TYPED { * Output type: bool * */ GHOSTTY_TERMINAL_DATA_VT_GROUND = 38, + + /** + * Whether the cursor is currently at a semantic shell prompt or input area. + * + * This depends on semantic prompt markers such as OSC 133. Returns false + * when semantic prompt information is unavailable or the alternate screen + * is active. + * + * Output type: bool * + */ + GHOSTTY_TERMINAL_DATA_CURSOR_AT_PROMPT = 39, GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyTerminalData; diff --git a/src/Surface.zig b/src/Surface.zig index 7ef2038b8..be8cbde8f 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -967,6 +967,17 @@ pub fn needsConfirmQuit(self: *Surface) bool { }; } +/// True if the surface cursor is currently at a shell prompt according to +/// semantic prompt integration. If shell integration is unavailable, this +/// conservatively returns false. +pub fn isAtPrompt(self: *Surface) bool { + if (self.child_exited) return false; + + self.renderer_state.mutex.lockUncancelable(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); + return self.io.terminal.cursorIsAtPrompt(); +} + /// Called from the app thread to handle mailbox messages to our specific /// surface. pub fn handleMessage(self: *Surface, msg: Message) !void { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index a380dc50c..689a3e4dc 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1606,6 +1606,11 @@ pub const CAPI = struct { return surface.core_surface.needsConfirmQuit(); } + /// Returns true if the surface cursor is currently at a semantic prompt. + export fn ghostty_surface_is_at_prompt(surface: *Surface) bool { + return surface.core_surface.isAtPrompt(); + } + /// Returns true if the surface process has exited. export fn ghostty_surface_process_exited(surface: *Surface) bool { return surface.core_surface.child_exited; diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 37cd1406b..24c5edb39 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -1355,6 +1355,7 @@ pub const TerminalData = enum(c_int) { continuation_max_bytes = 36, mode = 37, vt_ground = 38, + cursor_at_prompt = 39, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: TerminalData) type { @@ -1367,6 +1368,7 @@ pub const TerminalData = enum(c_int) { .viewport_active, .vt_processing_error, .vt_ground, + .cursor_at_prompt, => bool, .active_screen => TerminalScreen, .kitty_keyboard_flags => u8, @@ -1533,6 +1535,7 @@ fn getTyped( const mode = out.toMode() orelse return .invalid_value; out.value = t.modes.get(mode); }, + .cursor_at_prompt => out.* = t.cursorIsAtPrompt(), } return .success; @@ -2864,6 +2867,31 @@ test "get cursor_visible" { try testing.expect(!visible); } +test "get cursor_at_prompt" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &t, + 80, + 24, + )); + defer free(t); + + var at_prompt: bool = undefined; + try testing.expectEqual(Result.success, get(t, .cursor_at_prompt, @ptrCast(&at_prompt))); + try testing.expect(!at_prompt); + + const prompt_start = "\x1b]133;A\x07"; + vt_write(t, prompt_start, prompt_start.len); + try testing.expectEqual(Result.success, get(t, .cursor_at_prompt, @ptrCast(&at_prompt))); + try testing.expect(at_prompt); + + const alternate_screen = "\x1b[?1049h"; + vt_write(t, alternate_screen, alternate_screen.len); + try testing.expectEqual(Result.success, get(t, .cursor_at_prompt, @ptrCast(&at_prompt))); + try testing.expect(!at_prompt); +} + test "get active_screen" { var t: Terminal = null; try testing.expectEqual(Result.success, new( From c80c3736275412a0cf75588fb6b7e73fd9395fcd Mon Sep 17 00:00:00 2001 From: Vishal Kapur Date: Wed, 12 Aug 2026 13:24:21 -0700 Subject: [PATCH 2/2] Remove internal surface prompt query --- include/ghostty.h | 1 - src/Surface.zig | 11 ----------- src/apprt/embedded.zig | 5 ----- 3 files changed, 17 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index efcd2901e..5523654c4 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -1134,7 +1134,6 @@ GHOSTTY_API ghostty_app_t ghostty_surface_app(ghostty_surface_t); GHOSTTY_API ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t, ghostty_surface_context_e); GHOSTTY_API void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t); GHOSTTY_API bool ghostty_surface_needs_confirm_quit(ghostty_surface_t); -GHOSTTY_API bool ghostty_surface_is_at_prompt(ghostty_surface_t); GHOSTTY_API bool ghostty_surface_process_exited(ghostty_surface_t); GHOSTTY_API void ghostty_surface_refresh(ghostty_surface_t); GHOSTTY_API void ghostty_surface_draw(ghostty_surface_t); diff --git a/src/Surface.zig b/src/Surface.zig index be8cbde8f..7ef2038b8 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -967,17 +967,6 @@ pub fn needsConfirmQuit(self: *Surface) bool { }; } -/// True if the surface cursor is currently at a shell prompt according to -/// semantic prompt integration. If shell integration is unavailable, this -/// conservatively returns false. -pub fn isAtPrompt(self: *Surface) bool { - if (self.child_exited) return false; - - self.renderer_state.mutex.lockUncancelable(global.io()); - defer self.renderer_state.mutex.unlock(global.io()); - return self.io.terminal.cursorIsAtPrompt(); -} - /// Called from the app thread to handle mailbox messages to our specific /// surface. pub fn handleMessage(self: *Surface, msg: Message) !void { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 689a3e4dc..a380dc50c 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1606,11 +1606,6 @@ pub const CAPI = struct { return surface.core_surface.needsConfirmQuit(); } - /// Returns true if the surface cursor is currently at a semantic prompt. - export fn ghostty_surface_is_at_prompt(surface: *Surface) bool { - return surface.core_surface.isAtPrompt(); - } - /// Returns true if the surface process has exited. export fn ghostty_surface_process_exited(surface: *Surface) bool { return surface.core_surface.child_exited;