diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 501e2979f..9e2388d88 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -167,23 +167,30 @@ jobs:
- name: Build Benchmarks
run: nix develop -c zig build -Demit-bench
+ list-examples:
+ if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true'
+ needs: skip
+ runs-on: namespace-profile-ghostty-xsm
+ outputs:
+ dirs: ${{ steps.list.outputs.dirs }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - id: list
+ name: List example directories
+ run: |
+ dirs=$(ls example/*/build.zig.zon 2>/dev/null | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))')
+ echo "$dirs" | jq .
+ echo "dirs=$dirs" >> "$GITHUB_OUTPUT"
+
build-examples:
strategy:
fail-fast: false
matrix:
- dir:
- [
- c-vt,
- c-vt-key-encode,
- c-vt-paste,
- c-vt-sgr,
- zig-formatter,
- zig-vt,
- zig-vt-stream,
- ]
+ dir: ${{ fromJSON(needs.list-examples.outputs.dirs) }}
name: Example ${{ matrix.dir }}
- runs-on: namespace-profile-ghostty-sm
- needs: test
+ runs-on: namespace-profile-ghostty-xsm
+ needs: [test, list-examples]
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
diff --git a/example/AGENTS.md b/example/AGENTS.md
new file mode 100644
index 000000000..280b97e18
--- /dev/null
+++ b/example/AGENTS.md
@@ -0,0 +1,39 @@
+# Example Libghostty Projects
+
+Each example is a standalone project with its own `build.zig`,
+`build.zig.zon`, `README.md`, and `src/main.c` (or `.zig`). Examples are
+auto-discovered by CI via `example/*/build.zig.zon`, so no workflow file
+edits are needed when adding a new example.
+
+## Adding a New Example
+
+1. Copy an existing example directory (e.g., `c-vt-encode-focus/`) as a
+ starting point.
+2. Update `build.zig.zon`: change `.name`, generate a **new unique**
+ `.fingerprint` value (a random `u64` hex literal), and keep
+ `.minimum_zig_version` matching the others.
+3. Update `build.zig`: change the executable `.name` to match the directory.
+4. Write a `README.md` following the existing format.
+
+## Doxygen Snippet Tags
+
+Example source files use Doxygen `@snippet` tags so the corresponding
+header in `include/ghostty/vt/` can reference them. Wrap the relevant
+code with `//! [snippet-name]` markers:
+
+```c
+//! [my-snippet]
+int main() { ... }
+//! [my-snippet]
+```
+
+The header then uses `@snippet
/src/main.c my-snippet` instead of
+inline `@code` blocks. Never duplicate example code inline in the
+headers — always use `@snippet`. When modifying example code, keep the
+snippet markers in sync with the headers in `include/ghostty/vt/`.
+
+## Conventions
+
+- Executable names use underscores: `c_vt_encode_focus` (not hyphens).
+- All C examples link `ghostty-vt` via `lazyDependency("ghostty", ...)`.
+- `build.zig` files follow a common template — keep them consistent.
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 000000000..25e41aeeb
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,17 @@
+# Examples
+
+Standalone projects demonstrating the Ghostty library APIs.
+The directories starting with `c-` use the C API and the directories
+starting with `zig-` use the Zig API.
+
+Every example can be built and run using `zig build` and `zig build run`
+from within the respective example directory.
+Even the C API examples use the Zig build system (not the language) to
+build the project.
+
+## Running an Example
+
+```shell-session
+cd example/
+zig build run
+```
diff --git a/example/c-vt-encode-focus/README.md b/example/c-vt-encode-focus/README.md
new file mode 100644
index 000000000..f433e8808
--- /dev/null
+++ b/example/c-vt-encode-focus/README.md
@@ -0,0 +1,17 @@
+# Example: `ghostty-vt` Encode Focus
+
+This contains a simple example of how to use the `ghostty-vt` focus
+encoding API to encode focus gained/lost events into escape sequences.
+
+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-mouse-encode/build.zig b/example/c-vt-encode-focus/build.zig
similarity index 97%
rename from example/c-vt-mouse-encode/build.zig
rename to example/c-vt-encode-focus/build.zig
index 350d0a89e..2904371fb 100644
--- a/example/c-vt-mouse-encode/build.zig
+++ b/example/c-vt-encode-focus/build.zig
@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {
// Exe
const exe = b.addExecutable(.{
- .name = "c_vt_mouse_encode",
+ .name = "c_vt_encode_focus",
.root_module = exe_mod,
});
b.installArtifact(exe);
diff --git a/example/c-vt-encode-focus/build.zig.zon b/example/c-vt-encode-focus/build.zig.zon
new file mode 100644
index 000000000..0da20475c
--- /dev/null
+++ b/example/c-vt-encode-focus/build.zig.zon
@@ -0,0 +1,24 @@
+.{
+ .name = .c_vt_encode_focus,
+ .version = "0.0.0",
+ .fingerprint = 0x89f01fd829fcc550,
+ .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-encode-focus/src/main.c b/example/c-vt-encode-focus/src/main.c
new file mode 100644
index 000000000..15854792f
--- /dev/null
+++ b/example/c-vt-encode-focus/src/main.c
@@ -0,0 +1,20 @@
+#include
+#include
+
+//! [focus-encode]
+int main() {
+ char buf[8];
+ size_t written = 0;
+
+ GhosttyResult result = ghostty_focus_encode(
+ GHOSTTY_FOCUS_GAINED, buf, sizeof(buf), &written);
+
+ if (result == GHOSTTY_SUCCESS) {
+ printf("Encoded %zu bytes: ", written);
+ fwrite(buf, 1, written, stdout);
+ printf("\n");
+ }
+
+ return 0;
+}
+//! [focus-encode]
diff --git a/example/c-vt-key-encode/README.md b/example/c-vt-encode-key/README.md
similarity index 100%
rename from example/c-vt-key-encode/README.md
rename to example/c-vt-encode-key/README.md
diff --git a/example/c-vt-key-encode/build.zig b/example/c-vt-encode-key/build.zig
similarity index 97%
rename from example/c-vt-key-encode/build.zig
rename to example/c-vt-encode-key/build.zig
index b4b759744..de878a7ad 100644
--- a/example/c-vt-key-encode/build.zig
+++ b/example/c-vt-encode-key/build.zig
@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {
// Exe
const exe = b.addExecutable(.{
- .name = "c_vt_key_encode",
+ .name = "c_vt_encode_key",
.root_module = exe_mod,
});
b.installArtifact(exe);
diff --git a/example/c-vt-key-encode/build.zig.zon b/example/c-vt-encode-key/build.zig.zon
similarity index 100%
rename from example/c-vt-key-encode/build.zig.zon
rename to example/c-vt-encode-key/build.zig.zon
diff --git a/example/c-vt-encode-key/src/main.c b/example/c-vt-encode-key/src/main.c
new file mode 100644
index 000000000..99b782022
--- /dev/null
+++ b/example/c-vt-encode-key/src/main.c
@@ -0,0 +1,40 @@
+#include
+#include
+#include
+#include
+#include
+
+//! [key-encode]
+int main() {
+ // Create encoder
+ GhosttyKeyEncoder encoder;
+ GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
+ assert(result == GHOSTTY_SUCCESS);
+
+ // Enable Kitty keyboard protocol with all features
+ ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS,
+ &(uint8_t){GHOSTTY_KITTY_KEY_ALL});
+
+ // Create and configure key event for Ctrl+C press
+ GhosttyKeyEvent event;
+ result = ghostty_key_event_new(NULL, &event);
+ assert(result == GHOSTTY_SUCCESS);
+ ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_PRESS);
+ ghostty_key_event_set_key(event, GHOSTTY_KEY_C);
+ ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);
+
+ // Encode the key event
+ char buf[128];
+ size_t written = 0;
+ result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
+ assert(result == GHOSTTY_SUCCESS);
+
+ // Use the encoded sequence (e.g., write to terminal)
+ fwrite(buf, 1, written, stdout);
+
+ // Cleanup
+ ghostty_key_event_free(event);
+ ghostty_key_encoder_free(encoder);
+ return 0;
+}
+//! [key-encode]
diff --git a/example/c-vt-mouse-encode/README.md b/example/c-vt-encode-mouse/README.md
similarity index 100%
rename from example/c-vt-mouse-encode/README.md
rename to example/c-vt-encode-mouse/README.md
diff --git a/example/c-vt-encode-mouse/build.zig b/example/c-vt-encode-mouse/build.zig
new file mode 100644
index 000000000..4fdb353c0
--- /dev/null
+++ b/example/c-vt-encode-mouse/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_encode_mouse",
+ .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-mouse-encode/build.zig.zon b/example/c-vt-encode-mouse/build.zig.zon
similarity index 100%
rename from example/c-vt-mouse-encode/build.zig.zon
rename to example/c-vt-encode-mouse/build.zig.zon
diff --git a/example/c-vt-encode-mouse/src/main.c b/example/c-vt-encode-mouse/src/main.c
new file mode 100644
index 000000000..d75ed9c54
--- /dev/null
+++ b/example/c-vt-encode-mouse/src/main.c
@@ -0,0 +1,52 @@
+#include
+#include
+#include
+#include
+#include
+
+//! [mouse-encode]
+int main() {
+ // Create encoder
+ GhosttyMouseEncoder encoder;
+ GhosttyResult result = ghostty_mouse_encoder_new(NULL, &encoder);
+ assert(result == GHOSTTY_SUCCESS);
+
+ // Configure SGR format with normal tracking
+ ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_EVENT,
+ &(GhosttyMouseTrackingMode){GHOSTTY_MOUSE_TRACKING_NORMAL});
+ ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_FORMAT,
+ &(GhosttyMouseFormat){GHOSTTY_MOUSE_FORMAT_SGR});
+
+ // Set terminal geometry for coordinate mapping
+ ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE,
+ &(GhosttyMouseEncoderSize){
+ .size = sizeof(GhosttyMouseEncoderSize),
+ .screen_width = 800, .screen_height = 600,
+ .cell_width = 10, .cell_height = 20,
+ });
+
+ // Create and configure a left button press event
+ GhosttyMouseEvent event;
+ result = ghostty_mouse_event_new(NULL, &event);
+ assert(result == GHOSTTY_SUCCESS);
+ ghostty_mouse_event_set_action(event, GHOSTTY_MOUSE_ACTION_PRESS);
+ ghostty_mouse_event_set_button(event, GHOSTTY_MOUSE_BUTTON_LEFT);
+ ghostty_mouse_event_set_position(event,
+ (GhosttyMousePosition){.x = 50.0f, .y = 40.0f});
+
+ // Encode the mouse event
+ char buf[128];
+ size_t written = 0;
+ result = ghostty_mouse_encoder_encode(encoder, event,
+ buf, sizeof(buf), &written);
+ assert(result == GHOSTTY_SUCCESS);
+
+ // Use the encoded sequence (e.g., write to terminal)
+ fwrite(buf, 1, written, stdout);
+
+ // Cleanup
+ ghostty_mouse_event_free(event);
+ ghostty_mouse_encoder_free(encoder);
+ return 0;
+}
+//! [mouse-encode]
diff --git a/example/c-vt-key-encode/src/main.c b/example/c-vt-key-encode/src/main.c
deleted file mode 100644
index 82444f99d..000000000
--- a/example/c-vt-key-encode/src/main.c
+++ /dev/null
@@ -1,59 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-
-int main() {
- GhosttyKeyEncoder encoder;
- GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
- assert(result == GHOSTTY_SUCCESS);
-
- // Set kitty flags with all features enabled
- ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS, &(uint8_t){GHOSTTY_KITTY_KEY_ALL});
-
- // Create key event
- GhosttyKeyEvent event;
- result = ghostty_key_event_new(NULL, &event);
- assert(result == GHOSTTY_SUCCESS);
- ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_RELEASE);
- ghostty_key_event_set_key(event, GHOSTTY_KEY_CONTROL_LEFT);
- ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);
- printf("Encoding event: left ctrl release with all Kitty flags enabled\n");
-
- // Optionally, encode with null buffer to get required size. You can
- // skip this step and provide a sufficiently large buffer directly.
- // If there isn't enoug hspace, the function will return an out of memory
- // error.
- size_t required = 0;
- result = ghostty_key_encoder_encode(encoder, event, NULL, 0, &required);
- assert(result == GHOSTTY_OUT_OF_MEMORY);
- printf("Required buffer size: %zu bytes\n", required);
-
- // Encode the key event. We don't use our required size above because
- // that was just an example; we know 128 bytes is enough.
- char buf[128];
- size_t written = 0;
- result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
- assert(result == GHOSTTY_SUCCESS);
- printf("Encoded %zu bytes\n", written);
-
- // Print the encoded sequence (hex and string)
- printf("Hex: ");
- for (size_t i = 0; i < written; i++) printf("%02x ", (unsigned char)buf[i]);
- printf("\n");
-
- printf("String: ");
- for (size_t i = 0; i < written; i++) {
- if (buf[i] == 0x1b) {
- printf("\\x1b");
- } else {
- printf("%c", buf[i]);
- }
- }
- printf("\n");
-
- ghostty_key_event_free(event);
- ghostty_key_encoder_free(encoder);
- return 0;
-}
diff --git a/example/c-vt-modes/README.md b/example/c-vt-modes/README.md
new file mode 100644
index 000000000..bd43c1799
--- /dev/null
+++ b/example/c-vt-modes/README.md
@@ -0,0 +1,18 @@
+# Example: `ghostty-vt` Mode Utilities
+
+This contains a simple example of how to use the `ghostty-vt` mode
+utilities to pack and unpack terminal mode identifiers and encode
+DECRPM responses.
+
+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-modes/build.zig b/example/c-vt-modes/build.zig
new file mode 100644
index 000000000..1a4b3f8d8
--- /dev/null
+++ b/example/c-vt-modes/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_modes",
+ .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-modes/build.zig.zon b/example/c-vt-modes/build.zig.zon
new file mode 100644
index 000000000..bdfeefdca
--- /dev/null
+++ b/example/c-vt-modes/build.zig.zon
@@ -0,0 +1,24 @@
+.{
+ .name = .c_vt_modes,
+ .version = "0.0.0",
+ .fingerprint = 0x67ce079ebc70a02a,
+ .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-modes/src/main.c b/example/c-vt-modes/src/main.c
new file mode 100644
index 000000000..e957c9777
--- /dev/null
+++ b/example/c-vt-modes/src/main.c
@@ -0,0 +1,45 @@
+#include
+#include
+
+//! [modes-pack-unpack]
+void modes_example() {
+ // Create a mode for DEC mode 25 (cursor visible)
+ GhosttyMode tag = ghostty_mode_new(25, false);
+ printf("value=%u ansi=%d packed=0x%04x\n",
+ ghostty_mode_value(tag),
+ ghostty_mode_ansi(tag),
+ tag);
+
+ // Create a mode for ANSI mode 4 (insert mode)
+ GhosttyMode ansi_tag = ghostty_mode_new(4, true);
+ printf("value=%u ansi=%d packed=0x%04x\n",
+ ghostty_mode_value(ansi_tag),
+ ghostty_mode_ansi(ansi_tag),
+ ansi_tag);
+}
+//! [modes-pack-unpack]
+
+//! [modes-decrpm]
+void decrpm_example() {
+ char buf[32];
+ size_t written = 0;
+
+ // Encode a report that DEC mode 25 (cursor visible) is set
+ GhosttyResult result = ghostty_mode_report_encode(
+ GHOSTTY_MODE_CURSOR_VISIBLE,
+ GHOSTTY_MODE_REPORT_SET,
+ buf, sizeof(buf), &written);
+
+ if (result == GHOSTTY_SUCCESS) {
+ printf("Encoded %zu bytes: ", written);
+ fwrite(buf, 1, written, stdout);
+ printf("\n"); // prints: ESC[?25;1$y
+ }
+}
+//! [modes-decrpm]
+
+int main() {
+ modes_example();
+ decrpm_example();
+ return 0;
+}
diff --git a/example/c-vt-mouse-encode/src/main.c b/example/c-vt-mouse-encode/src/main.c
deleted file mode 100644
index 55f867252..000000000
--- a/example/c-vt-mouse-encode/src/main.c
+++ /dev/null
@@ -1,78 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-
-int main() {
- GhosttyMouseEncoder encoder;
- GhosttyResult result = ghostty_mouse_encoder_new(NULL, &encoder);
- assert(result == GHOSTTY_SUCCESS);
-
- // Set tracking mode to normal (button press/release)
- ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_EVENT,
- &(GhosttyMouseTrackingMode){GHOSTTY_MOUSE_TRACKING_NORMAL});
-
- // Set output format to SGR
- ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_FORMAT,
- &(GhosttyMouseFormat){GHOSTTY_MOUSE_FORMAT_SGR});
-
- // Set terminal geometry so the encoder can map pixel positions to cells
- ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE,
- &(GhosttyMouseEncoderSize){
- .size = sizeof(GhosttyMouseEncoderSize),
- .screen_width = 800,
- .screen_height = 600,
- .cell_width = 10,
- .cell_height = 20,
- .padding_top = 0,
- .padding_bottom = 0,
- .padding_right = 0,
- .padding_left = 0,
- });
-
- // Create mouse event: left button press at pixel position (50, 40)
- GhosttyMouseEvent event;
- result = ghostty_mouse_event_new(NULL, &event);
- assert(result == GHOSTTY_SUCCESS);
- ghostty_mouse_event_set_action(event, GHOSTTY_MOUSE_ACTION_PRESS);
- ghostty_mouse_event_set_button(event, GHOSTTY_MOUSE_BUTTON_LEFT);
- ghostty_mouse_event_set_position(event, (GhosttyMousePosition){.x = 50.0f, .y = 40.0f});
- printf("Encoding event: left button press at (50, 40) in SGR format\n");
-
- // Optionally, encode with null buffer to get required size. You can
- // skip this step and provide a sufficiently large buffer directly.
- // If there isn't enough space, the function will return an out of memory
- // error.
- size_t required = 0;
- result = ghostty_mouse_encoder_encode(encoder, event, NULL, 0, &required);
- assert(result == GHOSTTY_OUT_OF_MEMORY);
- printf("Required buffer size: %zu bytes\n", required);
-
- // Encode the mouse event. We don't use our required size above because
- // that was just an example; we know 128 bytes is enough.
- char buf[128];
- size_t written = 0;
- result = ghostty_mouse_encoder_encode(encoder, event, buf, sizeof(buf), &written);
- assert(result == GHOSTTY_SUCCESS);
- printf("Encoded %zu bytes\n", written);
-
- // Print the encoded sequence (hex and string)
- printf("Hex: ");
- for (size_t i = 0; i < written; i++) printf("%02x ", (unsigned char)buf[i]);
- printf("\n");
-
- printf("String: ");
- for (size_t i = 0; i < written; i++) {
- if (buf[i] == 0x1b) {
- printf("\\x1b");
- } else {
- printf("%c", buf[i]);
- }
- }
- printf("\n");
-
- ghostty_mouse_event_free(event);
- ghostty_mouse_encoder_free(encoder);
- return 0;
-}
diff --git a/example/c-vt-paste/src/main.c b/example/c-vt-paste/src/main.c
index 153861ca9..bb9e8e2a5 100644
--- a/example/c-vt-paste/src/main.c
+++ b/example/c-vt-paste/src/main.c
@@ -2,18 +2,23 @@
#include
#include
-int main() {
- // Test safe paste data
- const char *safe_data = "hello world";
+//! [paste-safety]
+void basic_example() {
+ const char* safe_data = "hello world";
+ const char* unsafe_data = "rm -rf /\n";
+
if (ghostty_paste_is_safe(safe_data, strlen(safe_data))) {
- printf("'%s' is safe to paste\n", safe_data);
+ printf("Safe to paste\n");
}
- // Test unsafe paste data with newline
- const char *unsafe_newline = "rm -rf /\n";
- if (!ghostty_paste_is_safe(unsafe_newline, strlen(unsafe_newline))) {
- printf("'%s' is UNSAFE - contains newline\n", unsafe_newline);
+ if (!ghostty_paste_is_safe(unsafe_data, strlen(unsafe_data))) {
+ printf("Unsafe! Contains newline\n");
}
+}
+//! [paste-safety]
+
+int main() {
+ basic_example();
// Test unsafe paste data with bracketed paste end sequence
const char *unsafe_escape = "evil\x1b[201~code";
diff --git a/example/c-vt-sgr/src/main.c b/example/c-vt-sgr/src/main.c
index 21a529726..e213c0c93 100644
--- a/example/c-vt-sgr/src/main.c
+++ b/example/c-vt-sgr/src/main.c
@@ -2,12 +2,43 @@
#include
#include
-int main() {
+//! [sgr-basic]
+void basic_example() {
// Create parser
GhosttySgrParser parser;
GhosttyResult result = ghostty_sgr_new(NULL, &parser);
assert(result == GHOSTTY_SUCCESS);
+ // Parse "bold, red foreground" sequence: ESC[1;31m
+ uint16_t params[] = {1, 31};
+ result = ghostty_sgr_set_params(parser, params, NULL, 2);
+ assert(result == GHOSTTY_SUCCESS);
+
+ // Iterate through attributes
+ GhosttySgrAttribute attr;
+ while (ghostty_sgr_next(parser, &attr)) {
+ switch (attr.tag) {
+ case GHOSTTY_SGR_ATTR_BOLD:
+ printf("Bold enabled\n");
+ break;
+ case GHOSTTY_SGR_ATTR_FG_8:
+ printf("Foreground color: %d\n", attr.value.fg_8);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Cleanup
+ ghostty_sgr_free(parser);
+}
+//! [sgr-basic]
+
+void advanced_example() {
+ GhosttySgrParser parser;
+ GhosttyResult result = ghostty_sgr_new(NULL, &parser);
+ assert(result == GHOSTTY_SUCCESS);
+
// Parse a complex SGR sequence from Kakoune
// This corresponds to the escape sequence:
// ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m
@@ -26,10 +57,9 @@ int main() {
result = ghostty_sgr_set_params(parser, params, separators, sizeof(params) / sizeof(params[0]));
assert(result == GHOSTTY_SUCCESS);
- printf("Parsing Kakoune SGR sequence:\n");
+ printf("\nParsing Kakoune SGR sequence:\n");
printf("ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m\n\n");
- // Iterate through attributes
GhosttySgrAttribute attr;
int count = 0;
while (ghostty_sgr_next(parser, &attr)) {
@@ -124,8 +154,11 @@ int main() {
}
printf("\nTotal attributes parsed: %d\n", count);
-
- // Cleanup
ghostty_sgr_free(parser);
+}
+
+int main() {
+ basic_example();
+ advanced_example();
return 0;
}
diff --git a/example/c-vt-size-report/README.md b/example/c-vt-size-report/README.md
new file mode 100644
index 000000000..0e6ef2c85
--- /dev/null
+++ b/example/c-vt-size-report/README.md
@@ -0,0 +1,17 @@
+# Example: `ghostty-vt` Size Report Encoding
+
+This contains a simple example of how to use the `ghostty-vt` size report
+encoding API to encode terminal size reports into escape sequences.
+
+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-size-report/build.zig b/example/c-vt-size-report/build.zig
new file mode 100644
index 000000000..fbd0f5e23
--- /dev/null
+++ b/example/c-vt-size-report/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_size_report",
+ .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-size-report/build.zig.zon b/example/c-vt-size-report/build.zig.zon
new file mode 100644
index 000000000..71d10d343
--- /dev/null
+++ b/example/c-vt-size-report/build.zig.zon
@@ -0,0 +1,24 @@
+.{
+ .name = .c_vt_size_report,
+ .version = "0.0.0",
+ .fingerprint = 0x17e8cdb658fab232,
+ .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-size-report/src/main.c b/example/c-vt-size-report/src/main.c
new file mode 100644
index 000000000..99e9c10dc
--- /dev/null
+++ b/example/c-vt-size-report/src/main.c
@@ -0,0 +1,27 @@
+#include
+#include
+
+//! [size-report-encode]
+int main() {
+ GhosttySizeReportSize size = {
+ .rows = 24,
+ .columns = 80,
+ .cell_width = 9,
+ .cell_height = 18,
+ };
+
+ char buf[64];
+ size_t written = 0;
+
+ GhosttyResult result = ghostty_size_report_encode(
+ GHOSTTY_SIZE_REPORT_MODE_2048, size, buf, sizeof(buf), &written);
+
+ if (result == GHOSTTY_SUCCESS) {
+ printf("Encoded %zu bytes: ", written);
+ fwrite(buf, 1, written, stdout);
+ printf("\n");
+ }
+
+ return 0;
+}
+//! [size-report-encode]
diff --git a/include/ghostty/vt.h b/include/ghostty/vt.h
index 5b670f80a..c6ab35fa3 100644
--- a/include/ghostty/vt.h
+++ b/include/ghostty/vt.h
@@ -45,8 +45,8 @@
*
* Complete working examples:
* - @ref c-vt/src/main.c - OSC parser example
- * - @ref c-vt-key-encode/src/main.c - Key encoding example
- * - @ref c-vt-mouse-encode/src/main.c - Mouse encoding example
+ * - @ref c-vt-encode-key/src/main.c - Key encoding example
+ * - @ref c-vt-encode-mouse/src/main.c - Mouse encoding example
* - @ref c-vt-paste/src/main.c - Paste safety check example
* - @ref c-vt-sgr/src/main.c - SGR parser example
* - @ref c-vt-formatter/src/main.c - Terminal formatter example
@@ -58,12 +58,12 @@
* extract command information, and retrieve command-specific data like window titles.
*/
-/** @example c-vt-key-encode/src/main.c
+/** @example c-vt-encode-key/src/main.c
* This example demonstrates how to use the key encoder to convert key events
* into terminal escape sequences using the Kitty keyboard protocol.
*/
-/** @example c-vt-mouse-encode/src/main.c
+/** @example c-vt-encode-mouse/src/main.c
* This example demonstrates how to use the mouse encoder to convert mouse events
* into terminal escape sequences using the SGR mouse format.
*/
diff --git a/include/ghostty/vt/focus.h b/include/ghostty/vt/focus.h
index ea3618bc8..e29fe6a66 100644
--- a/include/ghostty/vt/focus.h
+++ b/include/ghostty/vt/focus.h
@@ -20,26 +20,7 @@
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- *
- * int main() {
- * char buf[8];
- * size_t written = 0;
- *
- * GhosttyResult result = ghostty_focus_encode(
- * GHOSTTY_FOCUS_GAINED, buf, sizeof(buf), &written);
- *
- * if (result == GHOSTTY_SUCCESS) {
- * printf("Encoded %zu bytes: ", written);
- * fwrite(buf, 1, written, stdout);
- * printf("\n");
- * }
- *
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-encode-focus/src/main.c focus-encode
*
* @{
*/
diff --git a/include/ghostty/vt/key.h b/include/ghostty/vt/key.h
index e82a7596c..61b954753 100644
--- a/include/ghostty/vt/key.h
+++ b/include/ghostty/vt/key.h
@@ -27,49 +27,12 @@
* changing its properties.
* 4. Free the encoder with ghostty_key_encoder_free() when done
*
- * For a complete working example, see example/c-vt-key-encode in the
+ * For a complete working example, see example/c-vt-encode-key in the
* repository.
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- * #include
- *
- * int main() {
- * // Create encoder
- * GhosttyKeyEncoder encoder;
- * GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Enable Kitty keyboard protocol with all features
- * ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS,
- * &(uint8_t){GHOSTTY_KITTY_KEY_ALL});
- *
- * // Create and configure key event for Ctrl+C press
- * GhosttyKeyEvent event;
- * result = ghostty_key_event_new(NULL, &event);
- * assert(result == GHOSTTY_SUCCESS);
- * ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_PRESS);
- * ghostty_key_event_set_key(event, GHOSTTY_KEY_C);
- * ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);
- *
- * // Encode the key event
- * char buf[128];
- * size_t written = 0;
- * result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Use the encoded sequence (e.g., write to terminal)
- * fwrite(buf, 1, written, stdout);
- *
- * // Cleanup
- * ghostty_key_event_free(event);
- * ghostty_key_encoder_free(encoder);
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-encode-key/src/main.c key-encode
*
* ## Example: Encoding with Terminal State
*
diff --git a/include/ghostty/vt/modes.h b/include/ghostty/vt/modes.h
index 4454b2a50..53e148dd7 100644
--- a/include/ghostty/vt/modes.h
+++ b/include/ghostty/vt/modes.h
@@ -20,57 +20,14 @@
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- *
- * int main() {
- * // Create a mode for DEC mode 25 (cursor visible)
- * GhosttyMode tag = ghostty_mode_new(25, false);
- * printf("value=%u ansi=%d packed=0x%04x\n",
- * ghostty_mode_value(tag),
- * ghostty_mode_ansi(tag),
- * tag);
- *
- * // Create a mode for ANSI mode 4 (insert mode)
- * GhosttyMode ansi_tag = ghostty_mode_new(4, true);
- * printf("value=%u ansi=%d packed=0x%04x\n",
- * ghostty_mode_value(ansi_tag),
- * ghostty_mode_ansi(ansi_tag),
- * ansi_tag);
- *
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-modes/src/main.c modes-pack-unpack
*
* ## DECRPM Report Encoding
*
* Use ghostty_mode_report_encode() to encode a DECRPM response into a
* caller-provided buffer:
*
- * @code{.c}
- * #include
- * #include
- *
- * int main() {
- * char buf[32];
- * size_t written = 0;
- *
- * // Encode a report that DEC mode 25 (cursor visible) is set
- * GhosttyResult result = ghostty_mode_report_encode(
- * GHOSTTY_MODE_CURSOR_VISIBLE,
- * GHOSTTY_MODE_REPORT_SET,
- * buf, sizeof(buf), &written);
- *
- * if (result == GHOSTTY_SUCCESS) {
- * printf("Encoded %zu bytes: ", written);
- * fwrite(buf, 1, written, stdout);
- * printf("\n"); // prints: ESC[?25;1$y
- * }
- *
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-modes/src/main.c modes-decrpm
*
* @{
*/
diff --git a/include/ghostty/vt/mouse.h b/include/ghostty/vt/mouse.h
index f5f37ce7b..4ba5f52e3 100644
--- a/include/ghostty/vt/mouse.h
+++ b/include/ghostty/vt/mouse.h
@@ -24,61 +24,12 @@
* - Free the event with ghostty_mouse_event_free() or reuse it.
* 4. Free the encoder with ghostty_mouse_encoder_free() when done.
*
- * For a complete working example, see example/c-vt-mouse-encode in the
+ * For a complete working example, see example/c-vt-encode-mouse in the
* repository.
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- * #include
- *
- * int main() {
- * // Create encoder
- * GhosttyMouseEncoder encoder;
- * GhosttyResult result = ghostty_mouse_encoder_new(NULL, &encoder);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Configure SGR format with normal tracking
- * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_EVENT,
- * &(GhosttyMouseTrackingMode){GHOSTTY_MOUSE_TRACKING_NORMAL});
- * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_FORMAT,
- * &(GhosttyMouseFormat){GHOSTTY_MOUSE_FORMAT_SGR});
- *
- * // Set terminal geometry for coordinate mapping
- * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE,
- * &(GhosttyMouseEncoderSize){
- * .size = sizeof(GhosttyMouseEncoderSize),
- * .screen_width = 800, .screen_height = 600,
- * .cell_width = 10, .cell_height = 20,
- * });
- *
- * // Create and configure a left button press event
- * GhosttyMouseEvent event;
- * result = ghostty_mouse_event_new(NULL, &event);
- * assert(result == GHOSTTY_SUCCESS);
- * ghostty_mouse_event_set_action(event, GHOSTTY_MOUSE_ACTION_PRESS);
- * ghostty_mouse_event_set_button(event, GHOSTTY_MOUSE_BUTTON_LEFT);
- * ghostty_mouse_event_set_position(event,
- * (GhosttyMousePosition){.x = 50.0f, .y = 40.0f});
- *
- * // Encode the mouse event
- * char buf[128];
- * size_t written = 0;
- * result = ghostty_mouse_encoder_encode(encoder, event,
- * buf, sizeof(buf), &written);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Use the encoded sequence (e.g., write to terminal)
- * fwrite(buf, 1, written, stdout);
- *
- * // Cleanup
- * ghostty_mouse_event_free(event);
- * ghostty_mouse_encoder_free(encoder);
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-encode-mouse/src/main.c mouse-encode
*
* ## Example: Encoding with Terminal State
*
diff --git a/include/ghostty/vt/paste.h b/include/ghostty/vt/paste.h
index d90f303d4..b7212c801 100644
--- a/include/ghostty/vt/paste.h
+++ b/include/ghostty/vt/paste.h
@@ -18,26 +18,7 @@
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- * #include
- *
- * int main() {
- * const char* safe_data = "hello world";
- * const char* unsafe_data = "rm -rf /\n";
- *
- * if (ghostty_paste_is_safe(safe_data, strlen(safe_data))) {
- * printf("Safe to paste\n");
- * }
- *
- * if (!ghostty_paste_is_safe(unsafe_data, strlen(unsafe_data))) {
- * printf("Unsafe! Contains newline\n");
- * }
- *
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-paste/src/main.c paste-safety
*
* @{
*/
diff --git a/include/ghostty/vt/sgr.h b/include/ghostty/vt/sgr.h
index c81c1c87a..3b190a6b8 100644
--- a/include/ghostty/vt/sgr.h
+++ b/include/ghostty/vt/sgr.h
@@ -31,42 +31,7 @@
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- * #include
- *
- * int main() {
- * // Create parser
- * GhosttySgrParser parser;
- * GhosttyResult result = ghostty_sgr_new(NULL, &parser);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Parse "bold, red foreground" sequence: ESC[1;31m
- * uint16_t params[] = {1, 31};
- * result = ghostty_sgr_set_params(parser, params, NULL, 2);
- * assert(result == GHOSTTY_SUCCESS);
- *
- * // Iterate through attributes
- * GhosttySgrAttribute attr;
- * while (ghostty_sgr_next(parser, &attr)) {
- * switch (attr.tag) {
- * case GHOSTTY_SGR_ATTR_BOLD:
- * printf("Bold enabled\n");
- * break;
- * case GHOSTTY_SGR_ATTR_FG_8:
- * printf("Foreground color: %d\n", attr.value.fg_8);
- * break;
- * default:
- * break;
- * }
- * }
- *
- * // Cleanup
- * ghostty_sgr_free(parser);
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-sgr/src/main.c sgr-basic
*
* @{
*/
diff --git a/include/ghostty/vt/size_report.h b/include/ghostty/vt/size_report.h
index 4622b5bcf..b2eb08995 100644
--- a/include/ghostty/vt/size_report.h
+++ b/include/ghostty/vt/size_report.h
@@ -22,33 +22,7 @@
*
* ## Example
*
- * @code{.c}
- * #include
- * #include
- *
- * int main() {
- * GhosttySizeReportSize size = {
- * .rows = 24,
- * .columns = 80,
- * .cell_width = 9,
- * .cell_height = 18,
- * };
- *
- * char buf[64];
- * size_t written = 0;
- *
- * GhosttyResult result = ghostty_size_report_encode(
- * GHOSTTY_SIZE_REPORT_MODE_2048, size, buf, sizeof(buf), &written);
- *
- * if (result == GHOSTTY_SUCCESS) {
- * printf("Encoded %zu bytes: ", written);
- * fwrite(buf, 1, written, stdout);
- * printf("\n");
- * }
- *
- * return 0;
- * }
- * @endcode
+ * @snippet c-vt-size-report/src/main.c size-report-encode
*
* @{
*/