mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 08:01:45 +00:00
libghostty: -Dvt-features to compile out unused features
This introduces a `-Dvt-features` build option for libghostty-vt that compiles out optional feature areas, primarily so size-conscious embedders (e.g. wasm) can significantly trim the binary. The flag is similar to `-Dcpu`, `+feature` or `feature` to enable it, `-feature` to disable, magic word `all` to turn all features on or off. Example: `-Dvt-features=-all,+render-state` builds only the render state API. ### Sizes | Build | Bytes | Brotli | |---|---|---| | default (all features) | 876,500 | 218,309 | | web interactive (`-all,+render-state,+input-encode,+selection,+color,+grid-introspection`) | 661,119 | 168,994 | | read-only viewer (`-all,+render-state`) | 537,441 | 132,858 | | bare VT core (`-all`) | 515,422 | 125,756 | | xterm.js browser bundle (incl. renderers) | 488,663 | 99,311 | | @xterm/headless | 182,672 | 39,651 | Note: xterm versions are stable as of this commit.
This commit is contained in:
47
.github/workflows/test.yml
vendored
47
.github/workflows/test.yml
vendored
@@ -95,6 +95,7 @@ jobs:
|
||||
- build-flatpak
|
||||
- build-libghostty-vt
|
||||
- build-libghostty-vt-wasm
|
||||
- build-libghostty-vt-features
|
||||
- build-libghostty-vt-android
|
||||
- build-libghostty-vt-macos
|
||||
- build-libghostty-vt-windows
|
||||
@@ -726,6 +727,52 @@ jobs:
|
||||
echo "Verified ${artifact} requires simd128"
|
||||
done
|
||||
|
||||
# Compile-only checks for the -Dvt-features flags so that future changes
|
||||
# don't regress any feature combination.
|
||||
build-libghostty-vt-features:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Compile feature flag matrix
|
||||
run: |
|
||||
features="snapshot formatter selection render_state input_encode color grid_introspection glyph_protocol kitty_graphics"
|
||||
configs="-all"
|
||||
for f in $features; do configs="$configs -all,+$f"; done
|
||||
|
||||
for cfg in $configs; do
|
||||
echo "::group::-Dvt-features=$cfg"
|
||||
# The wasm lib build compiles the C API export block; the
|
||||
# test build compiles every module test without running any.
|
||||
nix develop -c zig build -Demit-lib-vt \
|
||||
"-Dvt-features=$cfg" \
|
||||
-Dtarget=wasm32-freestanding
|
||||
nix develop -c zig build test-lib-vt-build \
|
||||
"-Dvt-features=$cfg"
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
# lib-vt requires macOS runner for macOS/iOS builds because it requires the `apple_sdk` path
|
||||
build-libghostty-vt-macos:
|
||||
strategy:
|
||||
|
||||
21
PACKAGING.md
21
PACKAGING.md
@@ -141,6 +141,27 @@ Some notes for packaging the wasm module:
|
||||
a large performance win for VT parsing. If you target an unusual runtime
|
||||
without SIMD support, opt out with `-Dcpu=generic`.
|
||||
|
||||
- Optional feature areas can be compiled out with `-Dvt-features` to
|
||||
significantly reduce binary size. The flag takes comma-separated
|
||||
modifications applied to the default all-enabled feature set,
|
||||
`-Dcpu`-style: `+feature` (or bare `feature`) enables, `-feature`
|
||||
disables, and the special name `all` refers to every feature. Hyphens
|
||||
and underscores are interchangeable in feature names. For example, a
|
||||
read-only terminal viewer only needs the render state API:
|
||||
|
||||
```sh
|
||||
zig build -Demit-lib-vt -Dtarget=wasm32-freestanding \
|
||||
-Doptimize=ReleaseSmall -Dvt-features=-all,+render-state
|
||||
```
|
||||
|
||||
This roughly halves the compressed module size versus the default
|
||||
build. An interactive terminal typically wants
|
||||
`-Dvt-features=-all,+render-state,+input-encode,+selection,+color`.
|
||||
Disabled features drop both their C API exports and any escape
|
||||
sequence handling (the sequences are still consumed and safely
|
||||
ignored). See the `Features` struct in `src/terminal/build_options.zig`
|
||||
for the full list of features and what each one covers.
|
||||
|
||||
- `ReleaseSmall` is the recommended optimization mode for the web. Running
|
||||
the result through [Binaryen's](https://github.com/WebAssembly/binaryen)
|
||||
`wasm-opt -O3` shrinks it by roughly a further 10% without hurting
|
||||
|
||||
@@ -69,6 +69,10 @@ pub fn build(b: *std.Build) !void {
|
||||
"test-lib-vt",
|
||||
"Run libghostty-vt tests",
|
||||
);
|
||||
const test_lib_vt_build_step = b.step(
|
||||
"test-lib-vt-build",
|
||||
"Build libghostty-vt tests without running them (compile check)",
|
||||
);
|
||||
const test_valgrind_step = b.step(
|
||||
"test-valgrind",
|
||||
"Run tests under valgrind",
|
||||
@@ -329,6 +333,7 @@ pub fn build(b: *std.Build) !void {
|
||||
});
|
||||
const mod_vt_test_run = b.addRunArtifact(mod_vt_test);
|
||||
test_lib_vt_step.dependOn(&mod_vt_test_run.step);
|
||||
test_lib_vt_build_step.dependOn(&mod_vt_test.step);
|
||||
|
||||
const mod_vt_c_test = b.addTest(.{
|
||||
.root_module = mod.vt_c,
|
||||
@@ -336,6 +341,7 @@ pub fn build(b: *std.Build) !void {
|
||||
});
|
||||
const mod_vt_c_test_run = b.addRunArtifact(mod_vt_c_test);
|
||||
test_lib_vt_step.dependOn(&mod_vt_c_test_run.step);
|
||||
test_lib_vt_build_step.dependOn(&mod_vt_c_test.step);
|
||||
}
|
||||
|
||||
// Tests (skip when building libghostty-vt)
|
||||
|
||||
@@ -62,6 +62,10 @@ emit_xcframework: bool = false,
|
||||
emit_webdata: bool = false,
|
||||
emit_unicode_table_gen: bool = false,
|
||||
|
||||
/// Feature gates for libghostty-vt artifacts (-Dvt-features). The full
|
||||
/// Ghostty application ignores this and always enables everything.
|
||||
vt_features: TerminalBuildOptions.Features = .{},
|
||||
|
||||
/// True when Ghostty is being built as a dependency of another project
|
||||
/// rather than as the root project.
|
||||
is_dep: bool = false,
|
||||
@@ -421,6 +425,30 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf
|
||||
|
||||
config.emit_lib_vt = emit_lib_vt;
|
||||
|
||||
config.vt_features = features: {
|
||||
const list = b.option(
|
||||
[]const u8,
|
||||
"vt-features",
|
||||
"Comma-separated libghostty-vt feature modifications applied " ++
|
||||
"to the default all-enabled set, -Dcpu style: `+feature` " ++
|
||||
"or `feature` enables, `-feature` disables, and `all` " ++
|
||||
"means every feature (e.g. `-all,+render-state` for a " ++
|
||||
"render-only build). Only applies to lib artifacts.",
|
||||
) orelse break :features .{};
|
||||
break :features TerminalBuildOptions.Features.parse(list) catch {
|
||||
var valid: std.ArrayList(u8) = .empty;
|
||||
inline for (@typeInfo(TerminalBuildOptions.Features).@"struct".fields) |field| {
|
||||
if (valid.items.len > 0) try valid.appendSlice(b.allocator, ", ");
|
||||
try valid.appendSlice(b.allocator, field.name);
|
||||
}
|
||||
std.log.err(
|
||||
"-Dvt-features={s} contains an unknown feature. Valid features: all, {s}",
|
||||
.{ list, valid.items },
|
||||
);
|
||||
return error.UnknownVtFeature;
|
||||
};
|
||||
};
|
||||
|
||||
config.emit_exe = b.option(
|
||||
bool,
|
||||
"emit-exe",
|
||||
@@ -684,6 +712,12 @@ pub fn terminalOptions(
|
||||
.simd = self.simd,
|
||||
.oniguruma = true,
|
||||
.c_abi = false,
|
||||
// The application requires every feature; only lib artifacts
|
||||
// may trim them.
|
||||
.features = switch (artifact) {
|
||||
.ghostty => .{},
|
||||
.lib => self.vt_features,
|
||||
},
|
||||
.version = switch (artifact) {
|
||||
.ghostty => self.version,
|
||||
.lib => self.lib_version,
|
||||
|
||||
315
src/lib_vt.zig
315
src/lib_vt.zig
@@ -180,44 +180,50 @@ comptime {
|
||||
_ = @import("quirks_memset.zig");
|
||||
|
||||
const c = terminal.c_api;
|
||||
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
|
||||
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });
|
||||
@export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" });
|
||||
@export(&c.key_event_get_action, .{ .name = "ghostty_key_event_get_action" });
|
||||
@export(&c.key_event_set_key, .{ .name = "ghostty_key_event_set_key" });
|
||||
@export(&c.key_event_get_key, .{ .name = "ghostty_key_event_get_key" });
|
||||
@export(&c.key_event_set_mods, .{ .name = "ghostty_key_event_set_mods" });
|
||||
@export(&c.key_event_get_mods, .{ .name = "ghostty_key_event_get_mods" });
|
||||
@export(&c.key_event_set_consumed_mods, .{ .name = "ghostty_key_event_set_consumed_mods" });
|
||||
@export(&c.key_event_get_consumed_mods, .{ .name = "ghostty_key_event_get_consumed_mods" });
|
||||
@export(&c.key_event_set_composing, .{ .name = "ghostty_key_event_set_composing" });
|
||||
@export(&c.key_event_get_composing, .{ .name = "ghostty_key_event_get_composing" });
|
||||
@export(&c.key_event_set_utf8, .{ .name = "ghostty_key_event_set_utf8" });
|
||||
@export(&c.key_event_get_utf8, .{ .name = "ghostty_key_event_get_utf8" });
|
||||
@export(&c.key_event_set_unshifted_codepoint, .{ .name = "ghostty_key_event_set_unshifted_codepoint" });
|
||||
@export(&c.key_event_get_unshifted_codepoint, .{ .name = "ghostty_key_event_get_unshifted_codepoint" });
|
||||
@export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" });
|
||||
@export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" });
|
||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||
@export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" });
|
||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||
@export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" });
|
||||
@export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" });
|
||||
@export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" });
|
||||
@export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" });
|
||||
@export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" });
|
||||
@export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" });
|
||||
@export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" });
|
||||
@export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" });
|
||||
@export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" });
|
||||
@export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" });
|
||||
@export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" });
|
||||
@export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" });
|
||||
@export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" });
|
||||
@export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" });
|
||||
@export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" });
|
||||
@export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" });
|
||||
@export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" });
|
||||
const features = terminal.options;
|
||||
if (features.input_encode) {
|
||||
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
|
||||
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });
|
||||
@export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" });
|
||||
@export(&c.key_event_get_action, .{ .name = "ghostty_key_event_get_action" });
|
||||
@export(&c.key_event_set_key, .{ .name = "ghostty_key_event_set_key" });
|
||||
@export(&c.key_event_get_key, .{ .name = "ghostty_key_event_get_key" });
|
||||
@export(&c.key_event_set_mods, .{ .name = "ghostty_key_event_set_mods" });
|
||||
@export(&c.key_event_get_mods, .{ .name = "ghostty_key_event_get_mods" });
|
||||
@export(&c.key_event_set_consumed_mods, .{ .name = "ghostty_key_event_set_consumed_mods" });
|
||||
@export(&c.key_event_get_consumed_mods, .{ .name = "ghostty_key_event_get_consumed_mods" });
|
||||
@export(&c.key_event_set_composing, .{ .name = "ghostty_key_event_set_composing" });
|
||||
@export(&c.key_event_get_composing, .{ .name = "ghostty_key_event_get_composing" });
|
||||
@export(&c.key_event_set_utf8, .{ .name = "ghostty_key_event_set_utf8" });
|
||||
@export(&c.key_event_get_utf8, .{ .name = "ghostty_key_event_get_utf8" });
|
||||
@export(&c.key_event_set_unshifted_codepoint, .{ .name = "ghostty_key_event_set_unshifted_codepoint" });
|
||||
@export(&c.key_event_get_unshifted_codepoint, .{ .name = "ghostty_key_event_get_unshifted_codepoint" });
|
||||
@export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" });
|
||||
@export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" });
|
||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||
@export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" });
|
||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||
@export(&c.focus_encode, .{ .name = "ghostty_focus_encode" });
|
||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||
@export(&c.paste_encode, .{ .name = "ghostty_paste_encode" });
|
||||
@export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" });
|
||||
@export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" });
|
||||
@export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" });
|
||||
@export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" });
|
||||
@export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" });
|
||||
@export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" });
|
||||
@export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" });
|
||||
@export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" });
|
||||
@export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" });
|
||||
@export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" });
|
||||
@export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" });
|
||||
@export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" });
|
||||
@export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" });
|
||||
@export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" });
|
||||
@export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" });
|
||||
@export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" });
|
||||
@export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" });
|
||||
}
|
||||
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
|
||||
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
|
||||
@export(&c.osc_next, .{ .name = "ghostty_osc_next" });
|
||||
@@ -226,10 +232,7 @@ comptime {
|
||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||
@export(&c.color_scheme_report_encode, .{ .name = "ghostty_color_scheme_report_encode" });
|
||||
@export(&c.focus_encode, .{ .name = "ghostty_focus_encode" });
|
||||
@export(&c.mode_report_encode, .{ .name = "ghostty_mode_report_encode" });
|
||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||
@export(&c.paste_encode, .{ .name = "ghostty_paste_encode" });
|
||||
@export(&c.unicode_codepoint_width, .{ .name = "ghostty_unicode_codepoint_width" });
|
||||
@export(&c.unicode_grapheme_width, .{ .name = "ghostty_unicode_grapheme_width" });
|
||||
@export(&c.size_report_encode, .{ .name = "ghostty_size_report_encode" });
|
||||
@@ -237,21 +240,25 @@ comptime {
|
||||
@export(&c.style_is_default, .{ .name = "ghostty_style_is_default" });
|
||||
@export(&c.sys_log_stderr, .{ .name = "ghostty_sys_log_stderr" });
|
||||
@export(&c.sys_set, .{ .name = "ghostty_sys_set" });
|
||||
@export(&c.cell_get, .{ .name = "ghostty_cell_get" });
|
||||
@export(&c.cell_get_multi, .{ .name = "ghostty_cell_get_multi" });
|
||||
@export(&c.row_get, .{ .name = "ghostty_row_get" });
|
||||
@export(&c.row_get_multi, .{ .name = "ghostty_row_get_multi" });
|
||||
@export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" });
|
||||
@export(&c.color_contrast, .{ .name = "ghostty_color_contrast" });
|
||||
@export(&c.color_luminance, .{ .name = "ghostty_color_luminance" });
|
||||
@export(&c.color_parse, .{ .name = "ghostty_color_parse" });
|
||||
@export(&c.color_parse_palette_entry, .{ .name = "ghostty_color_parse_palette_entry" });
|
||||
@export(&c.color_parse_x11, .{ .name = "ghostty_color_parse_x11" });
|
||||
@export(&c.color_palette_default, .{ .name = "ghostty_color_palette_default" });
|
||||
@export(&c.color_palette_generate, .{ .name = "ghostty_color_palette_generate" });
|
||||
@export(&c.color_perceived_luminance, .{ .name = "ghostty_color_perceived_luminance" });
|
||||
@export(&c.color_x11_name_count, .{ .name = "ghostty_color_x11_name_count" });
|
||||
@export(&c.color_x11_names, .{ .name = "ghostty_color_x11_names" });
|
||||
if (features.grid_introspection) {
|
||||
@export(&c.cell_get, .{ .name = "ghostty_cell_get" });
|
||||
@export(&c.cell_get_multi, .{ .name = "ghostty_cell_get_multi" });
|
||||
@export(&c.row_get, .{ .name = "ghostty_row_get" });
|
||||
@export(&c.row_get_multi, .{ .name = "ghostty_row_get_multi" });
|
||||
}
|
||||
if (features.color) {
|
||||
@export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" });
|
||||
@export(&c.color_contrast, .{ .name = "ghostty_color_contrast" });
|
||||
@export(&c.color_luminance, .{ .name = "ghostty_color_luminance" });
|
||||
@export(&c.color_parse, .{ .name = "ghostty_color_parse" });
|
||||
@export(&c.color_parse_palette_entry, .{ .name = "ghostty_color_parse_palette_entry" });
|
||||
@export(&c.color_parse_x11, .{ .name = "ghostty_color_parse_x11" });
|
||||
@export(&c.color_palette_default, .{ .name = "ghostty_color_palette_default" });
|
||||
@export(&c.color_palette_generate, .{ .name = "ghostty_color_palette_generate" });
|
||||
@export(&c.color_perceived_luminance, .{ .name = "ghostty_color_perceived_luminance" });
|
||||
@export(&c.color_x11_name_count, .{ .name = "ghostty_color_x11_name_count" });
|
||||
@export(&c.color_x11_names, .{ .name = "ghostty_color_x11_names" });
|
||||
}
|
||||
@export(&c.sgr_new, .{ .name = "ghostty_sgr_new" });
|
||||
@export(&c.sgr_free, .{ .name = "ghostty_sgr_free" });
|
||||
@export(&c.sgr_reset, .{ .name = "ghostty_sgr_reset" });
|
||||
@@ -261,33 +268,39 @@ comptime {
|
||||
@export(&c.sgr_unknown_partial, .{ .name = "ghostty_sgr_unknown_partial" });
|
||||
@export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" });
|
||||
@export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" });
|
||||
@export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" });
|
||||
@export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" });
|
||||
@export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" });
|
||||
@export(&c.formatter_free, .{ .name = "ghostty_formatter_free" });
|
||||
@export(&c.terminal_selection_format_buf, .{ .name = "ghostty_terminal_selection_format_buf" });
|
||||
@export(&c.terminal_selection_format_alloc, .{ .name = "ghostty_terminal_selection_format_alloc" });
|
||||
@export(&c.render_state_new, .{ .name = "ghostty_render_state_new" });
|
||||
@export(&c.render_state_update, .{ .name = "ghostty_render_state_update" });
|
||||
@export(&c.render_state_begin_update, .{ .name = "ghostty_render_state_begin_update" });
|
||||
@export(&c.render_state_end_update, .{ .name = "ghostty_render_state_end_update" });
|
||||
@export(&c.render_state_get, .{ .name = "ghostty_render_state_get" });
|
||||
@export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" });
|
||||
@export(&c.render_state_set, .{ .name = "ghostty_render_state_set" });
|
||||
@export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" });
|
||||
@export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" });
|
||||
@export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" });
|
||||
@export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" });
|
||||
@export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" });
|
||||
@export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" });
|
||||
@export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" });
|
||||
@export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" });
|
||||
@export(&c.render_state_row_cells_next, .{ .name = "ghostty_render_state_row_cells_next" });
|
||||
@export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" });
|
||||
@export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" });
|
||||
@export(&c.render_state_row_cells_get_multi, .{ .name = "ghostty_render_state_row_cells_get_multi" });
|
||||
@export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" });
|
||||
@export(&c.render_state_free, .{ .name = "ghostty_render_state_free" });
|
||||
if (features.formatter) {
|
||||
@export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" });
|
||||
@export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" });
|
||||
@export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" });
|
||||
@export(&c.formatter_free, .{ .name = "ghostty_formatter_free" });
|
||||
}
|
||||
if (features.formatter and features.selection) {
|
||||
@export(&c.terminal_selection_format_buf, .{ .name = "ghostty_terminal_selection_format_buf" });
|
||||
@export(&c.terminal_selection_format_alloc, .{ .name = "ghostty_terminal_selection_format_alloc" });
|
||||
}
|
||||
if (features.render_state) {
|
||||
@export(&c.render_state_new, .{ .name = "ghostty_render_state_new" });
|
||||
@export(&c.render_state_update, .{ .name = "ghostty_render_state_update" });
|
||||
@export(&c.render_state_begin_update, .{ .name = "ghostty_render_state_begin_update" });
|
||||
@export(&c.render_state_end_update, .{ .name = "ghostty_render_state_end_update" });
|
||||
@export(&c.render_state_get, .{ .name = "ghostty_render_state_get" });
|
||||
@export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" });
|
||||
@export(&c.render_state_set, .{ .name = "ghostty_render_state_set" });
|
||||
@export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" });
|
||||
@export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" });
|
||||
@export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" });
|
||||
@export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" });
|
||||
@export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" });
|
||||
@export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" });
|
||||
@export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" });
|
||||
@export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" });
|
||||
@export(&c.render_state_row_cells_next, .{ .name = "ghostty_render_state_row_cells_next" });
|
||||
@export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" });
|
||||
@export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" });
|
||||
@export(&c.render_state_row_cells_get_multi, .{ .name = "ghostty_render_state_row_cells_get_multi" });
|
||||
@export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" });
|
||||
@export(&c.render_state_free, .{ .name = "ghostty_render_state_free" });
|
||||
}
|
||||
@export(&c.terminal_new, .{ .name = "ghostty_terminal_new" });
|
||||
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });
|
||||
@export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" });
|
||||
@@ -303,66 +316,82 @@ comptime {
|
||||
@export(&c.terminal_continuation_write, .{ .name = "ghostty_terminal_continuation_write" });
|
||||
@export(&c.terminal_continuation_buf, .{ .name = "ghostty_terminal_continuation_buf" });
|
||||
@export(&c.terminal_continuation_alloc, .{ .name = "ghostty_terminal_continuation_alloc" });
|
||||
@export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" });
|
||||
@export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" });
|
||||
@export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" });
|
||||
@export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" });
|
||||
@export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" });
|
||||
@export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" });
|
||||
@export(&c.terminal_selection_order, .{ .name = "ghostty_terminal_selection_order" });
|
||||
@export(&c.terminal_selection_ordered, .{ .name = "ghostty_terminal_selection_ordered" });
|
||||
@export(&c.terminal_selection_contains, .{ .name = "ghostty_terminal_selection_contains" });
|
||||
@export(&c.terminal_selection_equal, .{ .name = "ghostty_terminal_selection_equal" });
|
||||
@export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" });
|
||||
@export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" });
|
||||
@export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" });
|
||||
@export(&c.selection_gesture_event, .{ .name = "ghostty_selection_gesture_event" });
|
||||
@export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" });
|
||||
@export(&c.selection_gesture_get_multi, .{ .name = "ghostty_selection_gesture_get_multi" });
|
||||
@export(&c.selection_gesture_event_new, .{ .name = "ghostty_selection_gesture_event_new" });
|
||||
@export(&c.selection_gesture_event_free, .{ .name = "ghostty_selection_gesture_event_free" });
|
||||
@export(&c.selection_gesture_event_set, .{ .name = "ghostty_selection_gesture_event_set" });
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" });
|
||||
@export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
@export(&c.snapshot_encode, .{ .name = "ghostty_snapshot_encode" });
|
||||
@export(&c.snapshot_encode_buf, .{ .name = "ghostty_snapshot_encode_buf" });
|
||||
@export(&c.snapshot_encode_alloc, .{ .name = "ghostty_snapshot_encode_alloc" });
|
||||
@export(&c.snapshot_decoder_new, .{ .name = "ghostty_snapshot_decoder_new" });
|
||||
@export(&c.snapshot_decoder_new_buf, .{ .name = "ghostty_snapshot_decoder_new_buf" });
|
||||
@export(&c.snapshot_decoder_free, .{ .name = "ghostty_snapshot_decoder_free" });
|
||||
@export(&c.snapshot_decoder_set, .{ .name = "ghostty_snapshot_decoder_set" });
|
||||
@export(&c.snapshot_decoder_get, .{ .name = "ghostty_snapshot_decoder_get" });
|
||||
@export(&c.snapshot_decoder_get_multi, .{ .name = "ghostty_snapshot_decoder_get_multi" });
|
||||
@export(&c.snapshot_decoder_ready, .{ .name = "ghostty_snapshot_decoder_ready" });
|
||||
@export(&c.snapshot_decoder_next, .{ .name = "ghostty_snapshot_decoder_next" });
|
||||
@export(&c.snapshot_decoder_decode, .{ .name = "ghostty_snapshot_decoder_decode" });
|
||||
@export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" });
|
||||
@export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" });
|
||||
@export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" });
|
||||
@export(&c.kitty_graphics_image_get_multi, .{ .name = "ghostty_kitty_graphics_image_get_multi" });
|
||||
@export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" });
|
||||
@export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" });
|
||||
@export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" });
|
||||
@export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" });
|
||||
@export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" });
|
||||
@export(&c.kitty_graphics_placement_get_multi, .{ .name = "ghostty_kitty_graphics_placement_get_multi" });
|
||||
@export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" });
|
||||
@export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" });
|
||||
@export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" });
|
||||
@export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" });
|
||||
@export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" });
|
||||
@export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" });
|
||||
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
|
||||
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
|
||||
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });
|
||||
@export(&c.grid_ref_hyperlink_uri, .{ .name = "ghostty_grid_ref_hyperlink_uri" });
|
||||
@export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" });
|
||||
@export(&c.tracked_grid_ref_free, .{ .name = "ghostty_tracked_grid_ref_free" });
|
||||
@export(&c.tracked_grid_ref_has_value, .{ .name = "ghostty_tracked_grid_ref_has_value" });
|
||||
@export(&c.tracked_grid_ref_point, .{ .name = "ghostty_tracked_grid_ref_point" });
|
||||
@export(&c.tracked_grid_ref_set, .{ .name = "ghostty_tracked_grid_ref_set" });
|
||||
@export(&c.tracked_grid_ref_snapshot, .{ .name = "ghostty_tracked_grid_ref_snapshot" });
|
||||
if (features.selection) {
|
||||
@export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" });
|
||||
@export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" });
|
||||
@export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" });
|
||||
@export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" });
|
||||
@export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" });
|
||||
@export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" });
|
||||
@export(&c.terminal_selection_order, .{ .name = "ghostty_terminal_selection_order" });
|
||||
@export(&c.terminal_selection_ordered, .{ .name = "ghostty_terminal_selection_ordered" });
|
||||
@export(&c.terminal_selection_contains, .{ .name = "ghostty_terminal_selection_contains" });
|
||||
@export(&c.terminal_selection_equal, .{ .name = "ghostty_terminal_selection_equal" });
|
||||
@export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" });
|
||||
@export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" });
|
||||
@export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" });
|
||||
@export(&c.selection_gesture_event, .{ .name = "ghostty_selection_gesture_event" });
|
||||
@export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" });
|
||||
@export(&c.selection_gesture_get_multi, .{ .name = "ghostty_selection_gesture_get_multi" });
|
||||
@export(&c.selection_gesture_event_new, .{ .name = "ghostty_selection_gesture_event_new" });
|
||||
@export(&c.selection_gesture_event_free, .{ .name = "ghostty_selection_gesture_event_free" });
|
||||
@export(&c.selection_gesture_event_set, .{ .name = "ghostty_selection_gesture_event_set" });
|
||||
}
|
||||
// Selections are expressed in grid references, so the untracked
|
||||
// reference constructors are required by both features.
|
||||
if (features.grid_introspection or features.selection) {
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
}
|
||||
if (features.grid_introspection) {
|
||||
@export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" });
|
||||
}
|
||||
if (features.snapshot) {
|
||||
@export(&c.snapshot_encode, .{ .name = "ghostty_snapshot_encode" });
|
||||
@export(&c.snapshot_encode_buf, .{ .name = "ghostty_snapshot_encode_buf" });
|
||||
@export(&c.snapshot_encode_alloc, .{ .name = "ghostty_snapshot_encode_alloc" });
|
||||
@export(&c.snapshot_decoder_new, .{ .name = "ghostty_snapshot_decoder_new" });
|
||||
@export(&c.snapshot_decoder_new_buf, .{ .name = "ghostty_snapshot_decoder_new_buf" });
|
||||
@export(&c.snapshot_decoder_free, .{ .name = "ghostty_snapshot_decoder_free" });
|
||||
@export(&c.snapshot_decoder_set, .{ .name = "ghostty_snapshot_decoder_set" });
|
||||
@export(&c.snapshot_decoder_get, .{ .name = "ghostty_snapshot_decoder_get" });
|
||||
@export(&c.snapshot_decoder_get_multi, .{ .name = "ghostty_snapshot_decoder_get_multi" });
|
||||
@export(&c.snapshot_decoder_ready, .{ .name = "ghostty_snapshot_decoder_ready" });
|
||||
@export(&c.snapshot_decoder_next, .{ .name = "ghostty_snapshot_decoder_next" });
|
||||
@export(&c.snapshot_decoder_decode, .{ .name = "ghostty_snapshot_decoder_decode" });
|
||||
}
|
||||
if (features.kitty_graphics) {
|
||||
@export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" });
|
||||
@export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" });
|
||||
@export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" });
|
||||
@export(&c.kitty_graphics_image_get_multi, .{ .name = "ghostty_kitty_graphics_image_get_multi" });
|
||||
@export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" });
|
||||
@export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" });
|
||||
@export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" });
|
||||
@export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" });
|
||||
@export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" });
|
||||
@export(&c.kitty_graphics_placement_get_multi, .{ .name = "ghostty_kitty_graphics_placement_get_multi" });
|
||||
@export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" });
|
||||
@export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" });
|
||||
@export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" });
|
||||
@export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" });
|
||||
@export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" });
|
||||
@export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" });
|
||||
}
|
||||
if (features.grid_introspection) {
|
||||
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
|
||||
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
|
||||
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });
|
||||
@export(&c.grid_ref_hyperlink_uri, .{ .name = "ghostty_grid_ref_hyperlink_uri" });
|
||||
@export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" });
|
||||
@export(&c.tracked_grid_ref_free, .{ .name = "ghostty_tracked_grid_ref_free" });
|
||||
@export(&c.tracked_grid_ref_has_value, .{ .name = "ghostty_tracked_grid_ref_has_value" });
|
||||
@export(&c.tracked_grid_ref_point, .{ .name = "ghostty_tracked_grid_ref_point" });
|
||||
@export(&c.tracked_grid_ref_set, .{ .name = "ghostty_tracked_grid_ref_set" });
|
||||
}
|
||||
if (features.grid_introspection and features.snapshot) {
|
||||
@export(&c.tracked_grid_ref_snapshot, .{ .name = "ghostty_tracked_grid_ref_snapshot" });
|
||||
}
|
||||
@export(&c.build_info, .{ .name = "ghostty_build_info" });
|
||||
@export(&c.type_json, .{ .name = "ghostty_type_json" });
|
||||
@export(&c.alloc_alloc, .{ .name = "ghostty_alloc" });
|
||||
|
||||
@@ -15873,6 +15873,8 @@ test "Terminal: deleteLines wide char at right margin with full clear" {
|
||||
}
|
||||
|
||||
test "Terminal: glyph APC stores session glossary entries" {
|
||||
if (comptime !build_options.glyph_protocol) return error.SkipZigTest;
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const io_impl = testing.io;
|
||||
var t = try init(io_impl, alloc, .{ .cols = 80, .rows = 24 });
|
||||
|
||||
@@ -82,12 +82,16 @@ pub const Handler = struct {
|
||||
if (byte == ';') {
|
||||
const str = id.buf[0..id.len];
|
||||
if (std.mem.eql(u8, str, glyph.identifier)) {
|
||||
if (self.enabled.contains(.glyph)) {
|
||||
self.state = .{ .glyph = .init(
|
||||
alloc,
|
||||
self.max_bytes.get(.glyph) orelse
|
||||
Protocol.defaultMaxBytes(.glyph),
|
||||
) };
|
||||
if (comptime build_options.glyph_protocol) {
|
||||
if (self.enabled.contains(.glyph)) {
|
||||
self.state = .{ .glyph = .init(
|
||||
alloc,
|
||||
self.max_bytes.get(.glyph) orelse
|
||||
Protocol.defaultMaxBytes(.glyph),
|
||||
) };
|
||||
} else {
|
||||
self.state = .ignore;
|
||||
}
|
||||
} else {
|
||||
self.state = .ignore;
|
||||
}
|
||||
@@ -127,11 +131,13 @@ pub const Handler = struct {
|
||||
};
|
||||
} else unreachable,
|
||||
|
||||
.glyph => |*p| p.feed(byte) catch |err| {
|
||||
log.warn("glyph protocol error: {}", .{err});
|
||||
p.deinit();
|
||||
self.state = .ignore;
|
||||
},
|
||||
.glyph => |*p| if (comptime build_options.glyph_protocol) {
|
||||
p.feed(byte) catch |err| {
|
||||
log.warn("glyph protocol error: {}", .{err});
|
||||
p.deinit();
|
||||
self.state = .ignore;
|
||||
};
|
||||
} else unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,14 +197,14 @@ pub const Handler = struct {
|
||||
return;
|
||||
} else unreachable,
|
||||
|
||||
.glyph => |*p| {
|
||||
.glyph => |*p| if (comptime build_options.glyph_protocol) {
|
||||
p.feedSlice(rem) catch |err| {
|
||||
log.warn("glyph protocol error: {}", .{err});
|
||||
p.deinit();
|
||||
self.state = .ignore;
|
||||
};
|
||||
return;
|
||||
},
|
||||
} else unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +235,8 @@ pub const Handler = struct {
|
||||
},
|
||||
|
||||
.glyph => |*p| glyph_cmd: {
|
||||
if (comptime !build_options.glyph_protocol) unreachable;
|
||||
|
||||
const command = p.complete(p.alloc) catch |err| {
|
||||
log.warn("glyph protocol error: {}", .{err});
|
||||
break :glyph_cmd null;
|
||||
@@ -269,7 +277,10 @@ pub const State = union(enum) {
|
||||
void,
|
||||
|
||||
/// Glyph protocol
|
||||
glyph: glyph.CommandParser,
|
||||
glyph: if (build_options.glyph_protocol)
|
||||
glyph.CommandParser
|
||||
else
|
||||
void,
|
||||
|
||||
/// An unsupported APC retained for the optional unknown callback.
|
||||
/// Keep this after recognized protocol states so their tag values and
|
||||
@@ -280,7 +291,10 @@ pub const State = union(enum) {
|
||||
switch (self.*) {
|
||||
.inactive, .ignore, .identify => {},
|
||||
.unknown => |*v| v.deinit(),
|
||||
.glyph => |*v| v.deinit(),
|
||||
.glyph => |*v| if (comptime build_options.glyph_protocol)
|
||||
v.deinit()
|
||||
else
|
||||
unreachable,
|
||||
.kitty => |*v| if (comptime build_options.kitty_graphics)
|
||||
v.deinit()
|
||||
else
|
||||
@@ -415,7 +429,11 @@ pub const Command = union(enum) {
|
||||
else
|
||||
void,
|
||||
|
||||
glyph: glyph.Request,
|
||||
glyph: if (build_options.glyph_protocol)
|
||||
glyph.Request
|
||||
else
|
||||
void,
|
||||
|
||||
unknown: Unknown,
|
||||
|
||||
pub fn deinit(self: *Command, alloc: Allocator) void {
|
||||
@@ -425,7 +443,11 @@ pub const Command = union(enum) {
|
||||
else
|
||||
unreachable,
|
||||
|
||||
.glyph => |*v| v.deinit(alloc),
|
||||
.glyph => |*v| if (comptime build_options.glyph_protocol)
|
||||
v.deinit(alloc)
|
||||
else
|
||||
unreachable,
|
||||
|
||||
.unknown => |*v| v.deinit(alloc),
|
||||
}
|
||||
}
|
||||
@@ -636,6 +658,8 @@ test "garbage glyph command" {
|
||||
}
|
||||
|
||||
test "valid glyph command" {
|
||||
if (comptime !build_options.glyph_protocol) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
@@ -700,6 +724,8 @@ test "feedSlice unknown APC command is ignored" {
|
||||
}
|
||||
|
||||
test "feedSlice valid glyph command" {
|
||||
if (comptime !build_options.glyph_protocol) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
|
||||
@@ -36,9 +36,227 @@ pub const Options = struct {
|
||||
/// Options.
|
||||
c_abi: bool,
|
||||
|
||||
/// Optional feature gates, all enabled by default. See Features.
|
||||
features: Features = .{},
|
||||
|
||||
/// The version of the application.
|
||||
version: std.SemanticVersion,
|
||||
|
||||
/// Optional features for lib artifacts. Disabling a feature removes
|
||||
/// its C API exports and any stream-integrated handling from the
|
||||
/// build, primarily so size-conscious embedders (e.g. wasm) can trim
|
||||
/// the binary. The Ghostty application itself always builds with
|
||||
/// every feature enabled.
|
||||
///
|
||||
/// Field names are the accepted tokens for `-Dvt-features`, which
|
||||
/// acts like `-Dcpu`: comma-separated modifications applied on top
|
||||
/// of the default (all enabled) set. `+feature` and `feature`
|
||||
/// enable, `-feature` disables. The special name `all` refers to
|
||||
/// every feature, so `-Dvt-features=-all,+render-state` builds only
|
||||
/// the render state API. Hyphens in names are interchangeable with
|
||||
/// underscores. Fields are emitted verbatim as bool options on the
|
||||
/// `terminal_options` module.
|
||||
pub const Features = packed struct {
|
||||
/// Terminal state serialization: encode a full terminal (screen
|
||||
/// contents, scrollback, cursor, styles, modes, etc.) to a
|
||||
/// compact binary format and decode it back into a live
|
||||
/// terminal. Used for session persistence, window restoration,
|
||||
/// and moving terminal state between processes.
|
||||
///
|
||||
/// C API: `ghostty_snapshot_*` and, together with
|
||||
/// `grid_introspection`, `ghostty_tracked_grid_ref_snapshot`.
|
||||
snapshot: bool = true,
|
||||
|
||||
/// Textual export of terminal contents as plain text, VT
|
||||
/// (replayable escape sequences), or HTML, with optional extras
|
||||
/// such as cursor position, palette, modes, and tabstops. Used
|
||||
/// for copy-to-clipboard, dump-to-file, and debugging.
|
||||
///
|
||||
/// C API: `ghostty_formatter_*` and, together with `selection`,
|
||||
/// `ghostty_terminal_selection_format_*`.
|
||||
formatter: bool = true,
|
||||
|
||||
/// Text selection: select word/line/output/all, selection
|
||||
/// ordering, adjustment, and containment math, and the
|
||||
/// click/drag gesture state machine (single/double/triple
|
||||
/// click behaviors). Selections are expressed in grid
|
||||
/// references, so enabling this also keeps the untracked grid
|
||||
/// reference constructors even if `grid_introspection` is
|
||||
/// disabled.
|
||||
///
|
||||
/// C API: `ghostty_terminal_select_*`,
|
||||
/// `ghostty_terminal_selection_*`, `ghostty_selection_gesture_*`.
|
||||
selection: bool = true,
|
||||
|
||||
/// The render state API: a coherent, update-in-place view of
|
||||
/// the visible screen (rows, cells, styles, cursor, colors,
|
||||
/// palette) designed to drive a renderer at frame rates. This
|
||||
/// is the primary read path for embedders that draw the
|
||||
/// terminal.
|
||||
///
|
||||
/// C API: `ghostty_render_state_*`.
|
||||
render_state: bool = true,
|
||||
|
||||
/// Encoding of host input events into the byte sequences a
|
||||
/// terminal application expects: keyboard input (including the
|
||||
/// Kitty keyboard protocol), mouse reporting, focus reporting,
|
||||
/// and (bracketed) paste. Any interactive embedder needs this;
|
||||
/// a read-only viewer does not.
|
||||
///
|
||||
/// C API: `ghostty_key_event_*`, `ghostty_key_encoder_*`,
|
||||
/// `ghostty_mouse_event_*`, `ghostty_mouse_encoder_*`,
|
||||
/// `ghostty_focus_encode`, `ghostty_paste_*`.
|
||||
input_encode: bool = true,
|
||||
|
||||
/// Color utilities: luminance, perceived luminance, and
|
||||
/// contrast math (e.g. for minimum-contrast rendering), color
|
||||
/// string parsing (`#rrggbb`, X11 color names, XParseColor
|
||||
/// `rgb:`/`rgbi:` device specs), the default 256-color palette
|
||||
/// and palette generation from configurable bases, and the X11
|
||||
/// color name table itself.
|
||||
///
|
||||
/// C API: `ghostty_color_*`.
|
||||
color: bool = true,
|
||||
|
||||
/// Direct inspection of grid contents at a position via grid
|
||||
/// references: cell and row data getters (codepoints,
|
||||
/// graphemes, style, hyperlink URI, wrap state, ...) plus
|
||||
/// tracked grid references that stay valid across terminal
|
||||
/// updates. Used for hyperlink hover/open, accessibility, and
|
||||
/// tests; deliberately not built for render loops (use
|
||||
/// `render_state` for that).
|
||||
///
|
||||
/// C API: `ghostty_grid_ref_*`, `ghostty_tracked_grid_ref_*`,
|
||||
/// `ghostty_cell_get*`, `ghostty_row_get*`, and (shared with
|
||||
/// `selection`) `ghostty_terminal_grid_ref` /
|
||||
/// `ghostty_terminal_point_from_grid_ref`.
|
||||
grid_introspection: bool = true,
|
||||
|
||||
/// The APC glyph protocol: a Ghostty extension that lets
|
||||
/// terminal applications register custom font glyphs (glyf
|
||||
/// outlines) for private-use-area codepoints. Embedders that
|
||||
/// don't render registered glyphs can disable this; the
|
||||
/// sequences are still consumed and safely ignored.
|
||||
///
|
||||
/// No C API surface of its own; this gates the stream handling
|
||||
/// and glossary storage.
|
||||
glyph_protocol: bool = true,
|
||||
|
||||
/// The Kitty graphics protocol: APC command parsing, image
|
||||
/// transmission and storage (PNG decoding via the sys
|
||||
/// interface), and placement tracking, plus the read APIs a
|
||||
/// renderer uses to draw placed images. Disabled sequences are
|
||||
/// still consumed and safely ignored.
|
||||
///
|
||||
/// This requires the ability to get timestamps from the OS, so
|
||||
/// it is always disabled on freestanding targets (e.g.
|
||||
/// wasm32-freestanding) regardless of this setting.
|
||||
///
|
||||
/// C API: `ghostty_kitty_graphics_*`.
|
||||
kitty_graphics: bool = true,
|
||||
|
||||
pub fn parse(list: []const u8) error{UnknownFeature}!Features {
|
||||
// Modifications apply on top of the default set.
|
||||
var result: Features = .{};
|
||||
|
||||
var it = std.mem.splitAny(u8, list, ", ");
|
||||
while (it.next()) |raw| {
|
||||
if (raw.len == 0) continue;
|
||||
|
||||
var name = raw;
|
||||
var enable = true;
|
||||
switch (name[0]) {
|
||||
'+' => name = name[1..],
|
||||
'-' => {
|
||||
enable = false;
|
||||
name = name[1..];
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
// A dangling sign with no name is malformed.
|
||||
if (name.len == 0) return error.UnknownFeature;
|
||||
|
||||
// The special name "all" refers to every feature.
|
||||
if (std.mem.eql(u8, name, "all")) {
|
||||
inline for (@typeInfo(Features).@"struct".fields) |field| {
|
||||
@field(result, field.name) = enable;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var found = false;
|
||||
inline for (@typeInfo(Features).@"struct".fields) |field| {
|
||||
if (eqlName(name, field.name)) {
|
||||
@field(result, field.name) = enable;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) return error.UnknownFeature;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Feature name equality where '-' in the input matches '_' in
|
||||
/// the field name.
|
||||
fn eqlName(input: []const u8, name: []const u8) bool {
|
||||
if (input.len != name.len) return false;
|
||||
for (input, name) |a, b| {
|
||||
const norm: u8 = if (a == '-') '_' else a;
|
||||
if (norm != b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
test "parse" {
|
||||
const testing = std.testing;
|
||||
|
||||
const none: Features = @bitCast(
|
||||
@as(@typeInfo(Features).@"struct".backing_integer.?, 0),
|
||||
);
|
||||
|
||||
// Empty input keeps the defaults.
|
||||
try testing.expectEqual(Features{}, try Features.parse(""));
|
||||
|
||||
// All three token forms, comma separated.
|
||||
try testing.expectEqual(
|
||||
Features{ .snapshot = false },
|
||||
try Features.parse("-snapshot"),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Features{ .snapshot = false, .formatter = false },
|
||||
try Features.parse("-snapshot,+render_state,-formatter,color"),
|
||||
);
|
||||
|
||||
// The special "all" name refers to every feature.
|
||||
try testing.expectEqual(none, try Features.parse("-all"));
|
||||
try testing.expectEqual(Features{}, try Features.parse("-all,+all"));
|
||||
var render_only = none;
|
||||
render_only.render_state = true;
|
||||
try testing.expectEqual(
|
||||
render_only,
|
||||
try Features.parse("-all,+render-state"),
|
||||
);
|
||||
|
||||
// Hyphens are interchangeable with underscores.
|
||||
try testing.expectEqual(
|
||||
Features{ .input_encode = false, .grid_introspection = false },
|
||||
try Features.parse("-input-encode,-grid_introspection"),
|
||||
);
|
||||
|
||||
// Later tokens win.
|
||||
try testing.expectEqual(
|
||||
Features{},
|
||||
try Features.parse("-snapshot,+snapshot"),
|
||||
);
|
||||
|
||||
// Unknown names and dangling signs are errors.
|
||||
try testing.expectError(error.UnknownFeature, Features.parse("bogus"));
|
||||
try testing.expectError(error.UnknownFeature, Features.parse("-snapshot,-"));
|
||||
}
|
||||
};
|
||||
|
||||
/// Add the required build options for the terminal module.
|
||||
///
|
||||
/// The memory referenced by self is expected to stick around (it isn't
|
||||
@@ -55,25 +273,25 @@ pub const Options = struct {
|
||||
opts.addOption(bool, "simd", self.simd);
|
||||
opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety);
|
||||
|
||||
// Kitty graphics is almost always true. This used to be conditional on
|
||||
// some other factors but we've since generalized the implementation
|
||||
// to support optional PNG decoding, OS capabilities like filesystems,
|
||||
// etc. So its safe to always enable it and just have the
|
||||
// implementation deal with unsupported features as needed.
|
||||
//
|
||||
// We disable it on wasm32-freestanding because we at the least
|
||||
// require the ability to get timestamps and there is no way to
|
||||
// do that with freestanding targets.
|
||||
const target = m.resolved_target.?.result;
|
||||
opts.addOption(
|
||||
bool,
|
||||
"kitty_graphics",
|
||||
!(target.cpu.arch == .wasm32 and target.os.tag == .freestanding),
|
||||
);
|
||||
|
||||
// These are synthesized based on other options.
|
||||
opts.addOption(bool, "tmux_control_mode", self.oniguruma);
|
||||
|
||||
// Feature gates, emitted as flat bools (e.g. `options.snapshot`).
|
||||
const target = m.resolved_target.?.result;
|
||||
inline for (@typeInfo(Features).@"struct".fields) |field| {
|
||||
var value = @field(self.features, field.name);
|
||||
|
||||
// Kitty graphics requires the ability to get timestamps and
|
||||
// there is no way to do that on freestanding targets, so it
|
||||
// is always disabled there regardless of the feature setting.
|
||||
if (comptime std.mem.eql(u8, field.name, "kitty_graphics")) {
|
||||
if (target.cpu.arch == .wasm32 and target.os.tag == .freestanding)
|
||||
value = false;
|
||||
}
|
||||
|
||||
opts.addOption(bool, field.name, value);
|
||||
}
|
||||
|
||||
// Version information.
|
||||
opts.addOption(
|
||||
[]const u8,
|
||||
@@ -92,3 +310,7 @@ pub const Options = struct {
|
||||
m.addOptions("terminal_options", opts);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
_ = Options.Features;
|
||||
}
|
||||
|
||||
@@ -639,6 +639,8 @@ fn computeViewportPos(
|
||||
}
|
||||
|
||||
test "placement_iterator new/free" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(
|
||||
&lib.alloc.test_allocator,
|
||||
|
||||
@@ -5405,6 +5405,8 @@ test "set color sets dirty flag" {
|
||||
}
|
||||
|
||||
test "set glyph protocol disables APC handling and clears glossary" {
|
||||
if (comptime !build_options.glyph_protocol) return error.SkipZigTest;
|
||||
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
|
||||
@@ -1054,7 +1054,7 @@ pub const Handler = struct {
|
||||
}
|
||||
},
|
||||
|
||||
.glyph => |*glyph_req| {
|
||||
.glyph => |*glyph_req| if (comptime build_options.glyph_protocol) {
|
||||
const resp = self.terminal.glyphProtocol(alloc, glyph_req);
|
||||
if (resp) |r| resp_block: {
|
||||
// Don't waste time encoding if we can't write responses
|
||||
@@ -1824,6 +1824,8 @@ test "full reset" {
|
||||
}
|
||||
|
||||
test "glyph protocol APC with write_pty callback" {
|
||||
if (comptime !build_options.glyph_protocol) return error.SkipZigTest;
|
||||
|
||||
var t: Terminal = try .init(testing.io, testing.allocator, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user