From b30db91e69c68b724cc0410b5d703dd6ba66c2aa Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 26 Feb 2026 18:47:00 -0600 Subject: [PATCH 1/4] build: test that `ghostty.h` compiles during a normal `zig build test` --- build.zig | 22 ++++++++++++++++++++++ include/test.zig | 5 +++++ 2 files changed, 27 insertions(+) create mode 100644 include/test.zig diff --git a/build.zig b/build.zig index fa68b91b4..940105eee 100644 --- a/build.zig +++ b/build.zig @@ -54,6 +54,10 @@ pub fn build(b: *std.Build) !void { "update-translations", "Update translation files", ); + const test_include_ghostty_h_step = b.step( + "test-include-ghostty-h", + "Test include of ghostty.h", + ); // Ghostty resources like terminfo, shell integration, themes, etc. const resources = try buildpkg.GhosttyResources.init(b, &config, &deps); @@ -298,6 +302,24 @@ pub fn build(b: *std.Build) !void { // Normal tests always test our libghostty modules //test_step.dependOn(test_lib_vt_step); + const test_include_ghostty_h_exe = b.addTest(.{ + .name = "ghostty-include-ghostty-h", + .filters = test_filters, + .root_module = b.createModule(.{ + .root_source_file = b.path("include/test.zig"), + .target = config.baselineTarget(), + .optimize = .Debug, + .strip = false, + .omit_frame_pointer = false, + .unwind_tables = .sync, + .link_libc = true, + }), + }); + test_include_ghostty_h_exe.addIncludePath(b.path("include")); + const test_include_ghostty_h_run = b.addRunArtifact(test_include_ghostty_h_exe); + test_include_ghostty_h_step.dependOn(&test_include_ghostty_h_run.step); + test_step.dependOn(test_include_ghostty_h_step); + // Valgrind test running const valgrind_run = b.addSystemCommand(&.{ "valgrind", diff --git a/include/test.zig b/include/test.zig new file mode 100644 index 000000000..a691c9733 --- /dev/null +++ b/include/test.zig @@ -0,0 +1,5 @@ +test "ghostty_h" { + _ = @cImport({ + @cInclude("ghostty.h"); + }); +} From ea5b07d20f6ee76b54db67984b3e7926bc8c62e2 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 27 Feb 2026 00:52:47 -0600 Subject: [PATCH 2/4] core: add tests for `ghostty.h` * ensure that `ghostty.h` compiles during basic Zig tests * ensure that non-exhaustive enums are kept synchronized between `ghostty.h` and their respective Zig counterpart. * adjust some enums that varied from established conventions --- build.zig | 1 + include/ghostty.h | 8 +- .../Ghostty/FullscreenMode+Extension.swift | 6 +- .../Surface View/SurfaceView_AppKit.swift | 2 +- src/apprt/action.zig | 79 +++++++++++++++++++ src/apprt/ipc.zig | 11 ++- src/lib/enum.zig | 52 ++++++++++++ src/lib/main.zig | 1 + src/renderer.zig | 9 ++- src/terminal/mouse_shape.zig | 9 +++ src/terminal/osc.zig | 5 ++ 11 files changed, 172 insertions(+), 11 deletions(-) diff --git a/build.zig b/build.zig index 940105eee..e8bf0b183 100644 --- a/build.zig +++ b/build.zig @@ -292,6 +292,7 @@ pub fn build(b: *std.Build) !void { // Crash on x86_64 without this .use_llvm = true, }); + test_exe.root_module.addIncludePath(b.path("include")); if (config.emit_test_exe) b.installArtifact(test_exe); _ = try deps.add(test_exe); diff --git a/include/ghostty.h b/include/ghostty.h index ae41429de..19b6e0fa4 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -586,9 +586,9 @@ typedef enum { // apprt.action.Fullscreen typedef enum { GHOSTTY_FULLSCREEN_NATIVE, - GHOSTTY_FULLSCREEN_NON_NATIVE, - GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU, - GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH, + GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE, + GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_VISIBLE_MENU, + GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_PADDED_NOTCH, } ghostty_action_fullscreen_e; // apprt.action.FloatWindow @@ -718,7 +718,7 @@ typedef struct { // renderer.Health typedef enum { - GHOSTTY_RENDERER_HEALTH_OK, + GHOSTTY_RENDERER_HEALTH_HEALTHY, GHOSTTY_RENDERER_HEALTH_UNHEALTHY, } ghostty_action_renderer_health_e; diff --git a/macos/Sources/Ghostty/FullscreenMode+Extension.swift b/macos/Sources/Ghostty/FullscreenMode+Extension.swift index 0c0bba908..1970209cf 100644 --- a/macos/Sources/Ghostty/FullscreenMode+Extension.swift +++ b/macos/Sources/Ghostty/FullscreenMode+Extension.swift @@ -7,13 +7,13 @@ extension FullscreenMode { case GHOSTTY_FULLSCREEN_NATIVE: .native - case GHOSTTY_FULLSCREEN_NON_NATIVE: + case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE: .nonNative - case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU: + case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_VISIBLE_MENU: .nonNativeVisibleMenu - case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH: + case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_PADDED_NOTCH: .nonNativePaddedNotch default: diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index fb3b7f9df..581691ca9 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -678,7 +678,7 @@ extension Ghostty { guard let healthAny = notification.userInfo?["health"] else { return } guard let health = healthAny as? ghostty_action_renderer_health_e else { return } DispatchQueue.main.async { [weak self] in - self?.healthy = health == GHOSTTY_RENDERER_HEALTH_OK + self?.healthy = health == GHOSTTY_RENDERER_HEALTH_HEALTHY } } diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 06634856e..48c40faa1 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -7,6 +7,7 @@ const input = @import("../input.zig"); const renderer = @import("../renderer.zig"); const terminal = @import("../terminal/main.zig"); const CoreSurface = @import("../Surface.zig"); +const lib = @import("../lib/main.zig"); /// The target for an action. This is generally the thing that had focus /// while the action was made but the concept of "focus" is not guaranteed @@ -19,6 +20,10 @@ pub const Target = union(Key) { pub const Key = enum(c_int) { app, surface, + + test "ghostty_h Target.Key" { + try lib.checkGhosttyHEnum(Key, "GHOSTTY_TARGET_"); + } }; // Sync with: ghostty_target_u @@ -401,6 +406,10 @@ pub const Action = union(Key) { search_selected, readonly, copy_title_to_clipboard, + + test "ghostty_h Action.Key" { + try lib.checkGhosttyHEnum(Key, "GHOSTTY_ACTION_"); + } }; /// Sync with: ghostty_action_u @@ -482,6 +491,10 @@ pub const SplitDirection = enum(c_int) { down, left, up, + + test "ghostty_h SplitDirection" { + try lib.checkGhosttyHEnum(SplitDirection, "GHOSTTY_SPLIT_DIRECTION_"); + } }; // This is made extern (c_int) to make interop easier with our embedded @@ -494,6 +507,10 @@ pub const GotoSplit = enum(c_int) { left, down, right, + + test "ghostty_h GotoSplit" { + try lib.checkGhosttyHEnum(GotoSplit, "GHOSTTY_GOTO_SPLIT_"); + } }; // This is made extern (c_int) to make interop easier with our embedded @@ -501,6 +518,10 @@ pub const GotoSplit = enum(c_int) { pub const GotoWindow = enum(c_int) { previous, next, + + test "ghostty_h GotoWindow" { + try lib.checkGhosttyHEnum(GotoWindow, "GHOSTTY_GOTO_WINDOW_"); + } }; /// The amount to resize the split by and the direction to resize it in. @@ -513,6 +534,10 @@ pub const ResizeSplit = extern struct { down, left, right, + + test "ghostty_h ResizeSplit.Direction" { + try lib.checkGhosttyHEnum(Direction, "GHOSTTY_RESIZE_SPLIT_"); + } }; }; @@ -528,6 +553,11 @@ pub const GotoTab = enum(c_int) { next = -2, last = -3, _, + + // TODO: check non-exhaustive enums + // test "ghostty_h GotoTab" { + // try lib.checkGhosttyHEnum(GotoTab, "GHOSTTY_GOTO_TAB_"); + // } }; /// The fullscreen mode to toggle to if we're moving to fullscreen. @@ -539,18 +569,30 @@ pub const Fullscreen = enum(c_int) { macos_non_native, macos_non_native_visible_menu, macos_non_native_padded_notch, + + test "ghostty_h Fullscreen" { + try lib.checkGhosttyHEnum(Fullscreen, "GHOSTTY_FULLSCREEN_"); + } }; pub const FloatWindow = enum(c_int) { on, off, toggle, + + test "ghostty_h FloatWindow" { + try lib.checkGhosttyHEnum(FloatWindow, "GHOSTTY_FLOAT_WINDOW_"); + } }; pub const SecureInput = enum(c_int) { on, off, toggle, + + test "ghostty_h SecureInput" { + try lib.checkGhosttyHEnum(SecureInput, "GHOSTTY_SECURE_INPUT_"); + } }; /// The inspector mode to toggle to if we're toggling the inspector. @@ -558,27 +600,47 @@ pub const Inspector = enum(c_int) { toggle, show, hide, + + test "ghostty_h Inspector" { + try lib.checkGhosttyHEnum(Inspector, "GHOSTTY_INSPECTOR_"); + } }; pub const QuitTimer = enum(c_int) { start, stop, + + test "ghostty_h QuitTimer" { + try lib.checkGhosttyHEnum(QuitTimer, "GHOSTTY_QUIT_TIMER_"); + } }; pub const Readonly = enum(c_int) { off, on, + + test "ghostty_h Readonly" { + try lib.checkGhosttyHEnum(Readonly, "GHOSTTY_READONLY_"); + } }; pub const MouseVisibility = enum(c_int) { visible, hidden, + + test "ghostty_h MouseVisibility" { + try lib.checkGhosttyHEnum(MouseVisibility, "GHOSTTY_MOUSE_"); + } }; /// Whether to prompt for the surface title or tab title. pub const PromptTitle = enum(c_int) { surface, tab, + + test "ghostty_h PromptTitle" { + try lib.checkGhosttyHEnum(PromptTitle, "GHOSTTY_PROMPT_TITLE_"); + } }; pub const MouseOverLink = struct { @@ -782,6 +844,11 @@ pub const ColorKind = enum(c_int) { // 0+ values indicate a palette index _, + + // TODO: check non-non-exhaustive enums + // test "ghostty_h ColorKind" { + // try lib.checkGhosttyHEnum(ColorKind, "GHOSTTY_COLOR_KIND_"); + // } }; pub const ReloadConfig = extern struct { @@ -832,6 +899,10 @@ pub const OpenUrl = struct { /// The URL is known to contain HTML content. html, + + test "ghostty_h OpenUrl.Kind" { + try lib.checkGhosttyHEnum(Kind, "GHOSTTY_ACTION_OPEN_URL_KIND_"); + } }; // Sync with: ghostty_action_open_url_s @@ -858,6 +929,10 @@ pub const CloseTabMode = enum(c_int) { other, /// Close all tabs to the right of the current tab. right, + + test "ghostty_h CloseTabMode" { + try lib.checkGhosttyHEnum(CloseTabMode, "GHOSTTY_ACTION_CLOSE_TAB_MODE_"); + } }; pub const CommandFinished = struct { @@ -922,3 +997,7 @@ pub const SearchSelected = struct { }; } }; + +test { + _ = std.testing.refAllDeclsRecursive(@This()); +} diff --git a/src/apprt/ipc.zig b/src/apprt/ipc.zig index a6e8412e0..85a308d37 100644 --- a/src/apprt/ipc.zig +++ b/src/apprt/ipc.zig @@ -3,6 +3,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = @import("../quirks.zig").inlineAssert; +const lib = @import("../lib/main.zig"); pub const Errors = error{ /// The IPC failed. If a function returns this error, it's expected that @@ -22,6 +23,10 @@ pub const Target = union(Key) { pub const Key = enum(c_int) { class, detect, + + test "ghostty_h Target.Key" { + try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_TARGET_"); + } }; // Sync with: ghostty_ipc_target_u @@ -106,8 +111,12 @@ pub const Action = union(enum) { }; /// Sync with: ghostty_ipc_action_tag_e - pub const Key = enum(c_uint) { + pub const Key = enum(c_int) { new_window, + + test "ghostty_h Action.Key" { + try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_ACTION_"); + } }; /// Sync with: ghostty_ipc_action_u diff --git a/src/lib/enum.zig b/src/lib/enum.zig index 6fc759846..f34331052 100644 --- a/src/lib/enum.zig +++ b/src/lib/enum.zig @@ -91,3 +91,55 @@ test "abi by removing a key" { try testing.expectEqual(2, @intFromEnum(T.d)); } } + +/// Verify that for every key in enum T, there is a matching declaration in +/// `ghostty.h` with the correct value. +pub fn checkGhosttyHEnum(comptime T: type, comptime prefix: []const u8) !void { + const info = @typeInfo(T); + + try std.testing.expect(info == .@"enum"); + try std.testing.expect(info.@"enum".tag_type == c_int); + + @setEvalBranchQuota(1000000); + + const c = @cImport({ + @cInclude("ghostty.h"); + }); + + var set: std.EnumSet(T) = .initFull(); + + const c_decls = @typeInfo(c).@"struct".decls; + const enum_fields = info.@"enum".fields; + + inline for (enum_fields) |field| { + const upper_name = comptime u: { + var buf: [128]u8 = undefined; + break :u std.ascii.upperString(&buf, field.name); + }; + + inline for (c_decls) |decl| { + if (!comptime std.mem.startsWith(u8, decl.name, prefix)) continue; + + const suffix = decl.name[prefix.len..]; + + if (!comptime std.mem.eql(u8, suffix, upper_name)) continue; + + std.testing.expectEqual(field.value, @field(c, decl.name)) catch |e| { + std.log.err(@typeName(T) ++ " key " ++ field.name ++ " does not have the same backing int as " ++ decl.name, .{}); + return e; + }; + + set.remove(@enumFromInt(field.value)); + } + } + + std.testing.expect(set.count() == 0) catch |e| { + var it = set.iterator(); + while (it.next()) |v| { + var buf: [128]u8 = undefined; + const n = std.ascii.upperString(&buf, @tagName(v)); + std.log.err("ghostty.h is missing value for {s}{s}, {t}", .{ prefix, n, v }); + } + return e; + }; +} diff --git a/src/lib/main.zig b/src/lib/main.zig index 5a626b1e8..e4a67454e 100644 --- a/src/lib/main.zig +++ b/src/lib/main.zig @@ -5,6 +5,7 @@ const unionpkg = @import("union.zig"); pub const allocator = @import("allocator.zig"); pub const Enum = enumpkg.Enum; +pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum; pub const String = types.String; pub const Struct = @import("struct.zig").Struct; pub const Target = @import("target.zig").Target; diff --git a/src/renderer.zig b/src/renderer.zig index 9b5164e91..e4e5c94a6 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -31,6 +31,7 @@ pub const ScreenSize = size.ScreenSize; pub const GridSize = size.GridSize; pub const Padding = size.Padding; pub const cursorStyle = cursor.style; +pub const lib = @import("lib/main.zig"); /// The implementation to use for the renderer. This is comptime chosen /// so that every build has exactly one renderer implementation. @@ -44,8 +45,12 @@ pub const Renderer = switch (build_config.renderer) { /// renderers even if some states aren't reachable so that our API users /// can use the same enum for all renderers. pub const Health = enum(c_int) { - healthy = 0, - unhealthy = 1, + healthy, + unhealthy, + + test "ghostty_h Health" { + try lib.checkGhosttyHEnum(Health, "GHOSTTY_RENDERER_HEALTH_"); + } }; test { diff --git a/src/terminal/mouse_shape.zig b/src/terminal/mouse_shape.zig index 1e178c7ee..33481b860 100644 --- a/src/terminal/mouse_shape.zig +++ b/src/terminal/mouse_shape.zig @@ -1,5 +1,6 @@ const std = @import("std"); const build_options = @import("terminal_options"); +const lib = @import("../lib/main.zig"); /// The possible cursor shapes. Not all app runtimes support these shapes. /// The shapes are always based on the W3C supported cursor styles so we @@ -63,6 +64,10 @@ pub const MouseShape = enum(c_int) { .none => void, }; }; + + test "ghostty_h MouseShape" { + try lib.checkGhosttyHEnum(MouseShape, "GHOSTTY_MOUSE_SHAPE_"); + } }; const string_map = std.StaticStringMap(MouseShape).initComptime(.{ @@ -131,3 +136,7 @@ test "cursor shape from string" { const testing = std.testing; try testing.expectEqual(MouseShape.default, MouseShape.fromString("default").?); } + +test { + _ = std.testing.refAllDeclsRecursive(@This()); +} diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 43824ce01..167ca782e 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -15,6 +15,7 @@ const LibEnum = @import("../lib/enum.zig").Enum; const kitty_color = @import("kitty/color.zig"); const parsers = @import("osc/parsers.zig"); const encoding = @import("osc/encoding.zig"); +const lib = @import("../lib/main.zig"); pub const color = parsers.color; pub const semantic_prompt = parsers.semantic_prompt; @@ -197,6 +198,10 @@ pub const Command = union(Key) { @"error", indeterminate, pause, + + test "ghostty_h Command.ProgressReport.State" { + try lib.checkGhosttyHEnum(State, "GHOSTTY_PROGRESS_STATE_"); + } }; state: State, From cdf0dd15e90996db24f88a2104fc9c798b8d4cbf Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 27 Feb 2026 10:13:03 -0600 Subject: [PATCH 3/4] testing: use std.Build.TranslateC instead of @cImport --- build.zig | 29 ++++++----------------------- include/test.zig | 5 ----- src/apprt/action.zig | 36 ++++++++++++++++++------------------ src/apprt/ipc.zig | 4 ++-- src/lib/enum.zig | 12 ++++++------ src/renderer.zig | 2 +- src/terminal/mouse_shape.zig | 2 +- src/terminal/osc.zig | 2 +- 8 files changed, 35 insertions(+), 57 deletions(-) delete mode 100644 include/test.zig diff --git a/build.zig b/build.zig index e8bf0b183..3c65e7a3e 100644 --- a/build.zig +++ b/build.zig @@ -54,10 +54,6 @@ pub fn build(b: *std.Build) !void { "update-translations", "Update translation files", ); - const test_include_ghostty_h_step = b.step( - "test-include-ghostty-h", - "Test include of ghostty.h", - ); // Ghostty resources like terminfo, shell integration, themes, etc. const resources = try buildpkg.GhosttyResources.init(b, &config, &deps); @@ -292,7 +288,12 @@ pub fn build(b: *std.Build) !void { // Crash on x86_64 without this .use_llvm = true, }); - test_exe.root_module.addIncludePath(b.path("include")); + const ghostty_h = b.addTranslateC(.{ + .root_source_file = b.path("include/ghostty.h"), + .target = config.baselineTarget(), + .optimize = .Debug, + }); + test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule()); if (config.emit_test_exe) b.installArtifact(test_exe); _ = try deps.add(test_exe); @@ -303,24 +304,6 @@ pub fn build(b: *std.Build) !void { // Normal tests always test our libghostty modules //test_step.dependOn(test_lib_vt_step); - const test_include_ghostty_h_exe = b.addTest(.{ - .name = "ghostty-include-ghostty-h", - .filters = test_filters, - .root_module = b.createModule(.{ - .root_source_file = b.path("include/test.zig"), - .target = config.baselineTarget(), - .optimize = .Debug, - .strip = false, - .omit_frame_pointer = false, - .unwind_tables = .sync, - .link_libc = true, - }), - }); - test_include_ghostty_h_exe.addIncludePath(b.path("include")); - const test_include_ghostty_h_run = b.addRunArtifact(test_include_ghostty_h_exe); - test_include_ghostty_h_step.dependOn(&test_include_ghostty_h_run.step); - test_step.dependOn(test_include_ghostty_h_step); - // Valgrind test running const valgrind_run = b.addSystemCommand(&.{ "valgrind", diff --git a/include/test.zig b/include/test.zig deleted file mode 100644 index a691c9733..000000000 --- a/include/test.zig +++ /dev/null @@ -1,5 +0,0 @@ -test "ghostty_h" { - _ = @cImport({ - @cInclude("ghostty.h"); - }); -} diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 48c40faa1..55e80a700 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -21,7 +21,7 @@ pub const Target = union(Key) { app, surface, - test "ghostty_h Target.Key" { + test "ghostty.h Target.Key" { try lib.checkGhosttyHEnum(Key, "GHOSTTY_TARGET_"); } }; @@ -407,7 +407,7 @@ pub const Action = union(Key) { readonly, copy_title_to_clipboard, - test "ghostty_h Action.Key" { + test "ghostty.h Action.Key" { try lib.checkGhosttyHEnum(Key, "GHOSTTY_ACTION_"); } }; @@ -492,7 +492,7 @@ pub const SplitDirection = enum(c_int) { left, up, - test "ghostty_h SplitDirection" { + test "ghostty.h SplitDirection" { try lib.checkGhosttyHEnum(SplitDirection, "GHOSTTY_SPLIT_DIRECTION_"); } }; @@ -508,7 +508,7 @@ pub const GotoSplit = enum(c_int) { down, right, - test "ghostty_h GotoSplit" { + test "ghostty.h GotoSplit" { try lib.checkGhosttyHEnum(GotoSplit, "GHOSTTY_GOTO_SPLIT_"); } }; @@ -519,7 +519,7 @@ pub const GotoWindow = enum(c_int) { previous, next, - test "ghostty_h GotoWindow" { + test "ghostty.h GotoWindow" { try lib.checkGhosttyHEnum(GotoWindow, "GHOSTTY_GOTO_WINDOW_"); } }; @@ -535,7 +535,7 @@ pub const ResizeSplit = extern struct { left, right, - test "ghostty_h ResizeSplit.Direction" { + test "ghostty.h ResizeSplit.Direction" { try lib.checkGhosttyHEnum(Direction, "GHOSTTY_RESIZE_SPLIT_"); } }; @@ -555,7 +555,7 @@ pub const GotoTab = enum(c_int) { _, // TODO: check non-exhaustive enums - // test "ghostty_h GotoTab" { + // test "ghostty.h GotoTab" { // try lib.checkGhosttyHEnum(GotoTab, "GHOSTTY_GOTO_TAB_"); // } }; @@ -570,7 +570,7 @@ pub const Fullscreen = enum(c_int) { macos_non_native_visible_menu, macos_non_native_padded_notch, - test "ghostty_h Fullscreen" { + test "ghostty.h Fullscreen" { try lib.checkGhosttyHEnum(Fullscreen, "GHOSTTY_FULLSCREEN_"); } }; @@ -580,7 +580,7 @@ pub const FloatWindow = enum(c_int) { off, toggle, - test "ghostty_h FloatWindow" { + test "ghostty.h FloatWindow" { try lib.checkGhosttyHEnum(FloatWindow, "GHOSTTY_FLOAT_WINDOW_"); } }; @@ -590,7 +590,7 @@ pub const SecureInput = enum(c_int) { off, toggle, - test "ghostty_h SecureInput" { + test "ghostty.h SecureInput" { try lib.checkGhosttyHEnum(SecureInput, "GHOSTTY_SECURE_INPUT_"); } }; @@ -601,7 +601,7 @@ pub const Inspector = enum(c_int) { show, hide, - test "ghostty_h Inspector" { + test "ghostty.h Inspector" { try lib.checkGhosttyHEnum(Inspector, "GHOSTTY_INSPECTOR_"); } }; @@ -610,7 +610,7 @@ pub const QuitTimer = enum(c_int) { start, stop, - test "ghostty_h QuitTimer" { + test "ghostty.h QuitTimer" { try lib.checkGhosttyHEnum(QuitTimer, "GHOSTTY_QUIT_TIMER_"); } }; @@ -619,7 +619,7 @@ pub const Readonly = enum(c_int) { off, on, - test "ghostty_h Readonly" { + test "ghostty.h Readonly" { try lib.checkGhosttyHEnum(Readonly, "GHOSTTY_READONLY_"); } }; @@ -628,7 +628,7 @@ pub const MouseVisibility = enum(c_int) { visible, hidden, - test "ghostty_h MouseVisibility" { + test "ghostty.h MouseVisibility" { try lib.checkGhosttyHEnum(MouseVisibility, "GHOSTTY_MOUSE_"); } }; @@ -638,7 +638,7 @@ pub const PromptTitle = enum(c_int) { surface, tab, - test "ghostty_h PromptTitle" { + test "ghostty.h PromptTitle" { try lib.checkGhosttyHEnum(PromptTitle, "GHOSTTY_PROMPT_TITLE_"); } }; @@ -846,7 +846,7 @@ pub const ColorKind = enum(c_int) { _, // TODO: check non-non-exhaustive enums - // test "ghostty_h ColorKind" { + // test "ghostty.h ColorKind" { // try lib.checkGhosttyHEnum(ColorKind, "GHOSTTY_COLOR_KIND_"); // } }; @@ -900,7 +900,7 @@ pub const OpenUrl = struct { /// The URL is known to contain HTML content. html, - test "ghostty_h OpenUrl.Kind" { + test "ghostty.h OpenUrl.Kind" { try lib.checkGhosttyHEnum(Kind, "GHOSTTY_ACTION_OPEN_URL_KIND_"); } }; @@ -930,7 +930,7 @@ pub const CloseTabMode = enum(c_int) { /// Close all tabs to the right of the current tab. right, - test "ghostty_h CloseTabMode" { + test "ghostty.h CloseTabMode" { try lib.checkGhosttyHEnum(CloseTabMode, "GHOSTTY_ACTION_CLOSE_TAB_MODE_"); } }; diff --git a/src/apprt/ipc.zig b/src/apprt/ipc.zig index 85a308d37..b37647e02 100644 --- a/src/apprt/ipc.zig +++ b/src/apprt/ipc.zig @@ -24,7 +24,7 @@ pub const Target = union(Key) { class, detect, - test "ghostty_h Target.Key" { + test "ghostty.h Target.Key" { try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_TARGET_"); } }; @@ -114,7 +114,7 @@ pub const Action = union(enum) { pub const Key = enum(c_int) { new_window, - test "ghostty_h Action.Key" { + test "ghostty.h Action.Key" { try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_ACTION_"); } }; diff --git a/src/lib/enum.zig b/src/lib/enum.zig index f34331052..bdec2ab88 100644 --- a/src/lib/enum.zig +++ b/src/lib/enum.zig @@ -93,18 +93,18 @@ test "abi by removing a key" { } /// Verify that for every key in enum T, there is a matching declaration in -/// `ghostty.h` with the correct value. +/// `ghostty.h` with the correct value. This should only ever be called inside a `test` +/// because the `ghostty.h` module is only available then. pub fn checkGhosttyHEnum(comptime T: type, comptime prefix: []const u8) !void { const info = @typeInfo(T); try std.testing.expect(info == .@"enum"); try std.testing.expect(info.@"enum".tag_type == c_int); + try std.testing.expect(info.@"enum".is_exhaustive == true); @setEvalBranchQuota(1000000); - const c = @cImport({ - @cInclude("ghostty.h"); - }); + const c = @import("ghostty.h"); var set: std.EnumSet(T) = .initFull(); @@ -137,8 +137,8 @@ pub fn checkGhosttyHEnum(comptime T: type, comptime prefix: []const u8) !void { var it = set.iterator(); while (it.next()) |v| { var buf: [128]u8 = undefined; - const n = std.ascii.upperString(&buf, @tagName(v)); - std.log.err("ghostty.h is missing value for {s}{s}, {t}", .{ prefix, n, v }); + const upper_string = std.ascii.upperString(&buf, @tagName(v)); + std.log.err("ghostty.h is missing value for {s}{s}", .{ prefix, upper_string }); } return e; }; diff --git a/src/renderer.zig b/src/renderer.zig index e4e5c94a6..747556847 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -48,7 +48,7 @@ pub const Health = enum(c_int) { healthy, unhealthy, - test "ghostty_h Health" { + test "ghostty.h Health" { try lib.checkGhosttyHEnum(Health, "GHOSTTY_RENDERER_HEALTH_"); } }; diff --git a/src/terminal/mouse_shape.zig b/src/terminal/mouse_shape.zig index 33481b860..b5c6ac4d1 100644 --- a/src/terminal/mouse_shape.zig +++ b/src/terminal/mouse_shape.zig @@ -65,7 +65,7 @@ pub const MouseShape = enum(c_int) { }; }; - test "ghostty_h MouseShape" { + test "ghostty.h MouseShape" { try lib.checkGhosttyHEnum(MouseShape, "GHOSTTY_MOUSE_SHAPE_"); } }; diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 167ca782e..3d34b36fc 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -199,7 +199,7 @@ pub const Command = union(Key) { indeterminate, pause, - test "ghostty_h Command.ProgressReport.State" { + test "ghostty.h Command.ProgressReport.State" { try lib.checkGhosttyHEnum(State, "GHOSTTY_PROGRESS_STATE_"); } }; From c61c8f9e300d67de3f983fcb0296972ef1472b38 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Feb 2026 08:38:07 -0800 Subject: [PATCH 4/4] minor moving stuff --- build.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 3c65e7a3e..f9d861b19 100644 --- a/build.zig +++ b/build.zig @@ -288,14 +288,16 @@ pub fn build(b: *std.Build) !void { // Crash on x86_64 without this .use_llvm = true, }); + if (config.emit_test_exe) b.installArtifact(test_exe); + _ = try deps.add(test_exe); + + // Verify our internal libghostty header. const ghostty_h = b.addTranslateC(.{ .root_source_file = b.path("include/ghostty.h"), .target = config.baselineTarget(), .optimize = .Debug, }); test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule()); - if (config.emit_test_exe) b.installArtifact(test_exe); - _ = try deps.add(test_exe); // Normal test running const test_run = b.addRunArtifact(test_exe);