From b68e1a292652becaa5d2d6ee5df0f0d875b5546c Mon Sep 17 00:00:00 2001 From: Chris Gonzales Date: Wed, 3 Jun 2026 06:57:15 -0500 Subject: [PATCH] apprt: add ghostty_surface_read_text_styled to the embedding API Same as ghostty_surface_read_text but formats with emit=.vt so SGR sequences (colors/styles) are preserved. Palette indices are resolved to direct RGB via the terminal's current palette (mirroring the styled clipboard path in Surface.zig), soft-wrapped lines are not unwrapped so one output line == one grid row, and trailing whitespace is preserved. Motivation: embedders rendering an overview/minimap of the scrollback need per-cell color, which the plain read_text strips. The formatter already supports this; this just exposes it through the C API. --- include/ghostty.h | 3 ++ src/apprt/embedded.zig | 70 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index fbfe3ee2c..d55a1f2ef 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -1160,6 +1160,9 @@ GHOSTTY_API bool ghostty_surface_read_selection(ghostty_surface_t, ghostty_text_ GHOSTTY_API bool ghostty_surface_read_text(ghostty_surface_t, ghostty_selection_s, ghostty_text_s*); +GHOSTTY_API bool ghostty_surface_read_text_styled(ghostty_surface_t, + ghostty_selection_s, + ghostty_text_s*); GHOSTTY_API void ghostty_surface_free_text(ghostty_surface_t, ghostty_text_s*); #ifdef __APPLE__ diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 7310159cc..ecb2e7e82 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1677,6 +1677,76 @@ pub const CAPI = struct { return true; } + /// Same as ghostty_surface_read_text but emits VT escape sequences + /// (predominantly SGR) so colors and styles are preserved. Differences + /// from the plain variant: + /// + /// - Palette-indexed colors are resolved to direct RGB using the + /// terminal's current palette, so the output is self-contained. + /// - Soft-wrapped lines are NOT unwrapped: one emitted line always + /// corresponds to one grid row, which lets callers map output rows + /// to scrollback rows (e.g. for overview/minimap rendering). + /// - Trailing whitespace is preserved. + /// + /// Free the result with ghostty_surface_free_text. The viewport fields + /// of the result are not populated by this function. + /// + /// Like ghostty_surface_read_text, this is an expensive operation, so + /// callers should cache results and throttle calls. + export fn ghostty_surface_read_text_styled( + surface: *Surface, + sel: Selection, + result: *Text, + ) bool { + const core_surface = &surface.core_surface; + core_surface.renderer_state.mutex.lock(); + defer core_surface.renderer_state.mutex.unlock(); + + const core_sel = sel.core( + core_surface.renderer_state.terminal.screens.active, + ) orelse return false; + + // Mirror the styled-clipboard configuration from + // Surface.completeClipboardReadWrite: resolve palette colors to + // direct RGB and carry the terminal's default fg/bg. + const term = &core_surface.io.terminal; + var formatter: terminal.formatter.ScreenFormatter = .init( + term.screens.active, + .{ + .emit = .vt, + .unwrap = false, + .trim = false, + .background = term.colors.background.get(), + .foreground = term.colors.foreground.get(), + .palette = &term.colors.palette.current, + }, + ); + formatter.content = .{ .selection = core_sel }; + + var aw: std.Io.Writer.Allocating = .init(global.alloc); + defer aw.deinit(); + formatter.format(&aw.writer) catch |err| { + log.warn("error reading styled text err={}", .{err}); + return false; + }; + + const text = aw.toOwnedSliceSentinel(0) catch |err| { + log.warn("error reading styled text err={}", .{err}); + return false; + }; + + result.* = .{ + .tl_px_x = -1, + .tl_px_y = -1, + .offset_start = 0, + .offset_len = 0, + .text = text.ptr, + .text_len = text.len, + }; + + return true; + } + export fn ghostty_surface_free_text(_: *Surface, ptr: *Text) void { ptr.deinit(); }