From 741f1d129a44151a8d51f813a9eabc39dc4d4df1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Mar 2026 18:32:49 -0700 Subject: [PATCH] example/c-vt-stream --- example/c-vt-stream/README.md | 19 ++++++++ example/c-vt-stream/build.zig | 42 ++++++++++++++++++ example/c-vt-stream/build.zig.zon | 24 ++++++++++ example/c-vt-stream/src/main.c | 74 +++++++++++++++++++++++++++++++ include/ghostty/vt/terminal.h | 4 ++ 5 files changed, 163 insertions(+) create mode 100644 example/c-vt-stream/README.md create mode 100644 example/c-vt-stream/build.zig create mode 100644 example/c-vt-stream/build.zig.zon create mode 100644 example/c-vt-stream/src/main.c diff --git a/example/c-vt-stream/README.md b/example/c-vt-stream/README.md new file mode 100644 index 000000000..6620537ed --- /dev/null +++ b/example/c-vt-stream/README.md @@ -0,0 +1,19 @@ +# Example: VT Stream Processing in C + +This contains a simple example of how to use `ghostty_terminal_vt_write` +to parse and process VT sequences in C. This is the C equivalent of +the `zig-vt-stream` example, ideal for read-only terminal applications +such as replay tooling, CI log viewers, and PaaS builder output. + +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-stream/build.zig b/example/c-vt-stream/build.zig new file mode 100644 index 000000000..21575ddd6 --- /dev/null +++ b/example/c-vt-stream/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_stream", + .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-stream/build.zig.zon b/example/c-vt-stream/build.zig.zon new file mode 100644 index 000000000..4c37e852c --- /dev/null +++ b/example/c-vt-stream/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_stream, + .version = "0.0.0", + .fingerprint = 0xd5bb3fc45e3f4dfc, + .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-stream/src/main.c b/example/c-vt-stream/src/main.c new file mode 100644 index 000000000..7063e1f14 --- /dev/null +++ b/example/c-vt-stream/src/main.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +int main(void) { + //! [vt-stream-init] + // Create a terminal + GhosttyTerminal terminal; + GhosttyTerminalOptions opts = { + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }; + GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts); + assert(result == GHOSTTY_SUCCESS); + //! [vt-stream-init] + + //! [vt-stream-write] + // Feed VT data into the terminal + const char *text = "Hello, World!\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + + // ANSI color codes: ESC[1;32m = bold green, ESC[0m = reset + text = "\x1b[1;32mGreen Text\x1b[0m\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + + // Cursor positioning: ESC[1;1H = move to row 1, column 1 + text = "\x1b[1;1HTop-left corner\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + + // Cursor movement: ESC[5B = move down 5 lines + text = "\x1b[5B"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + text = "Moved down!\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + + // Erase line: ESC[2K = clear entire line + text = "\x1b[2K"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + text = "New content\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + + // Multiple lines + text = "Line A\r\nLine B\r\nLine C\r\n"; + ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text)); + //! [vt-stream-write] + + //! [vt-stream-read] + // Get the final terminal state as a plain string using the formatter + GhosttyFormatterTerminalOptions fmt_opts = + GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions); + fmt_opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN; + fmt_opts.trim = true; + + GhosttyFormatter formatter; + result = ghostty_formatter_terminal_new(NULL, &formatter, terminal, fmt_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); + + fwrite(buf, 1, len, stdout); + printf("\n"); + + ghostty_free(NULL, buf, len); + ghostty_formatter_free(formatter); + //! [vt-stream-read] + + ghostty_terminal_free(terminal); + return 0; +} diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index ec0b46508..9660576f4 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -34,6 +34,10 @@ extern "C" { * Once a terminal session is up and running, you can configure a key encoder * to write keyboard input via ghostty_key_encoder_setopt_from_terminal(). * + * ### Example: VT stream processing + * @snippet c-vt-stream/src/main.c vt-stream-init + * @snippet c-vt-stream/src/main.c vt-stream-write + * * ## Effects * * By default, the terminal sequence processing with ghostty_terminal_vt_write()