Expose semantic prompt state through C APIs

This commit is contained in:
Vishal Kapur
2026-05-27 11:14:12 -07:00
parent d2eeb734b0
commit bdb566068e
5 changed files with 56 additions and 0 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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(