From e8f53539120e73e06c5bd82ab39dbf1499bf9311 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 May 2026 13:47:32 -0700 Subject: [PATCH] example/c-vt-selection --- example/c-vt-selection/README.md | 18 +++++ example/c-vt-selection/build.zig | 42 +++++++++++ example/c-vt-selection/build.zig.zon | 24 +++++++ example/c-vt-selection/src/main.c | 102 +++++++++++++++++++++++++++ include/ghostty/vt/selection.h | 4 ++ 5 files changed, 190 insertions(+) create mode 100644 example/c-vt-selection/README.md create mode 100644 example/c-vt-selection/build.zig create mode 100644 example/c-vt-selection/build.zig.zon create mode 100644 example/c-vt-selection/src/main.c diff --git a/example/c-vt-selection/README.md b/example/c-vt-selection/README.md new file mode 100644 index 000000000..c88f7a11d --- /dev/null +++ b/example/c-vt-selection/README.md @@ -0,0 +1,18 @@ +# Example: `ghostty-vt` Selection + +This contains a simple example of how to use the `ghostty-vt` terminal, +grid reference, selection, and formatter APIs to derive selections such as a +word, semantic command line, command output, and all visible content. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-selection/build.zig b/example/c-vt-selection/build.zig new file mode 100644 index 000000000..49f7c8cb3 --- /dev/null +++ b/example/c-vt-selection/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_selection", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-selection/build.zig.zon b/example/c-vt-selection/build.zig.zon new file mode 100644 index 000000000..d09800a51 --- /dev/null +++ b/example/c-vt-selection/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_selection, + .version = "0.0.0", + .fingerprint = 0xb2c2f1a828086fef, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-selection/src/main.c b/example/c-vt-selection/src/main.c new file mode 100644 index 000000000..ea638bbe8 --- /dev/null +++ b/example/c-vt-selection/src/main.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +//! [selection-main] +static void vt_write(GhosttyTerminal terminal, const char *s) { + ghostty_terminal_vt_write(terminal, (const uint8_t *)s, strlen(s)); +} + +static GhosttyGridRef ref_at(GhosttyTerminal terminal, uint16_t x, uint16_t y) { + GhosttyGridRef ref = GHOSTTY_INIT_SIZED(GhosttyGridRef); + GhosttyPoint point = { + .tag = GHOSTTY_POINT_TAG_ACTIVE, + .value = { .coordinate = { .x = x, .y = y } }, + }; + + GhosttyResult result = ghostty_terminal_grid_ref(terminal, point, &ref); + assert(result == GHOSTTY_SUCCESS); + return ref; +} + +static void print_selection( + GhosttyTerminal terminal, + const char *label, + const GhosttySelection *selection) { + GhosttyFormatterTerminalOptions opts = GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions); + opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN; + opts.trim = true; + opts.selection = selection; + + GhosttyFormatter formatter; + GhosttyResult result = ghostty_formatter_terminal_new( + NULL, &formatter, terminal, opts); + assert(result == GHOSTTY_SUCCESS); + + uint8_t *buf = NULL; + size_t len = 0; + result = ghostty_formatter_format_alloc(formatter, NULL, &buf, &len); + assert(result == GHOSTTY_SUCCESS); + + printf("%s: ", label); + fwrite(buf, 1, len, stdout); + printf("\n"); + + ghostty_free(NULL, buf, len); + ghostty_formatter_free(formatter); +} + +int main() { + GhosttyTerminal terminal; + GhosttyTerminalOptions opts = { + .cols = 80, + .rows = 8, + .max_scrollback = 0, + }; + GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts); + assert(result == GHOSTTY_SUCCESS); + + // A realistic shell transcript with OSC 133 semantic prompt markers. + // Ghostty uses these markers to distinguish prompt/input from command + // output for semantic line and output selections. + vt_write(terminal, + "\033]133;A\007$ " // Prompt starts: "$ " + "\033]133;B\007git status" // Input starts: "git status" + "\033]133;C\007\r\n" // Output starts after Enter + "On branch main\r\n" + "nothing to commit, working tree clean"); + + GhosttySelection selection = GHOSTTY_INIT_SIZED(GhosttySelection); + + // Double-click style word selection under the cursor. + GhosttyTerminalSelectWordOptions word = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectWordOptions); + word.ref = ref_at(terminal, 6, 0); // the "status" in "git status" + result = ghostty_terminal_select_word(terminal, &word, &selection); + assert(result == GHOSTTY_SUCCESS); + print_selection(terminal, "word", &selection); + + // Triple-click style line selection. With semantic prompt boundaries enabled, + // this selects only the input area rather than the leading "$ " prompt. + GhosttyTerminalSelectLineOptions line = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectLineOptions); + line.ref = ref_at(terminal, 2, 0); // the "git status" input area + line.semantic_prompt_boundary = true; + result = ghostty_terminal_select_line(terminal, &line, &selection); + assert(result == GHOSTTY_SUCCESS); + print_selection(terminal, "line", &selection); + + // Select exactly the command output for the command under the cursor. + result = ghostty_terminal_select_output( + terminal, ref_at(terminal, 0, 1), &selection); + assert(result == GHOSTTY_SUCCESS); + print_selection(terminal, "output", &selection); + + // Select all visible content. + result = ghostty_terminal_select_all(terminal, &selection); + assert(result == GHOSTTY_SUCCESS); + print_selection(terminal, "all", &selection); + + ghostty_terminal_free(terminal); + return 0; +} +//! [selection-main] diff --git a/include/ghostty/vt/selection.h b/include/ghostty/vt/selection.h index 89a722673..654396aef 100644 --- a/include/ghostty/vt/selection.h +++ b/include/ghostty/vt/selection.h @@ -30,6 +30,10 @@ extern "C" { * for the endpoints and reconstruct a GhosttySelection from fresh snapshots * when needed. * + * ## Examples + * + * @snippet c-vt-selection/src/main.c selection-main + * * @{ */