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