Commit 08a06c0

Eric Bower  ·  2026-04-04 10:22:43 -0400 EDT
parent d0e3ed1
refactor: kitty key press logic to be more generic

fix: src/util.zig tests were not being run :sad:
3 files changed,  +30, -13
+15, -1
......@@ -64,8 +64,22 @@ pub fn build(b: *std.Build) void {
6464 // Test
6565 {
6666 const test_step = b.step("test", "Run unit tests");
67+ const test_module = b.addModule("test", .{
68+ .root_source_file = b.path("src/test.zig"),
69+ .target = target,
70+ .optimize = optimize,
71+ });
72+ if (b.lazyDependency("ghostty", .{
73+ .target = target,
74+ .optimize = optimize,
75+ })) |dep| {
76+ test_module.addImport(
77+ "ghostty-vt",
78+ dep.module("ghostty-vt"),
79+ );
80+ }
6781 const exe_unit_tests = b.addTest(.{
68- .root_module = exe_mod,
82+ .root_module = test_module,
6983 });
7084 const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
7185 test_step.dependOn(&run_exe_unit_tests.step);
+5, -0
......@@ -0,0 +1,5 @@
1+comptime {
2+ _ = @import("main.zig");
3+ _ = @import("util.zig");
4+ _ = @import("socket.zig");
5+}
+10, -12
......@@ -220,24 +220,22 @@ pub fn findTaskExitMarker(output: []const u8) ?u8 {
220220 /// and alternate key sub-fields from the kitty protocol's progressive
221221 /// enhancement flags.
222222 pub fn isKittyCtrlBackslash(buf: []const u8) bool {
223- return isKittyCtrlKey(buf, 92);
223+ return detectCsiKeyPress(buf, 92, 0b100);
224224 }
225225
226-fn isKittyCtrlKey(buf: []const u8, key_code: u32) bool {
226+fn detectCsiKeyPress(buf: []const u8, expected_key: u32, expected_mods: u32) bool {
227227 // Scan for any CSI u sequence encoding the given Ctrl+key in the buffer.
228228 // The sequence can appear at any offset (e.g. preceded by other input).
229229 var i: usize = 0;
230230 while (i + 2 < buf.len) : (i += 1) {
231231 if (buf[i] == 0x1b and buf[i + 1] == '[') {
232- if (parseKittyCtrlKey(buf[i + 2 ..], key_code)) return true;
232+ if (isKeyPressed(buf[i + 2 ..], expected_key, expected_mods)) return true;
233233 }
234234 }
235235 return false;
236236 }
237237
238-/// Parse a CSI u sequence (after the `\x1b[` prefix) and return true if it
239-/// encodes a Ctrl+key press or repeat event for the given key code.
240-fn parseKittyCtrlKey(buf: []const u8, expected_key: u32) bool {
238+fn isKeyPressed(buf: []const u8, expected_key: u32, expected_mods: u32) bool {
241239 var pos: usize = 0;
242240
243241 // 1. Parse key code.
......@@ -259,11 +257,11 @@ fn parseKittyCtrlKey(buf: []const u8, expected_key: u32) bool {
259257 if (mod_encoded < 1) return false;
260258 const mod_raw = mod_encoded - 1;
261259
262- // 5. Ctrl must be the only intentional modifier. Lock modifiers
260+ // 5. Only accept intentional modifiers. Lock modifiers
263261 // (caps_lock=0b1000000, num_lock=0b10000000) are tolerated because
264262 // they are ambient state, not deliberate key combinations.
265263 const intentional_mods = mod_raw & 0b00111111;
266- if (intentional_mods != 0b100) return false;
264+ if (intentional_mods != expected_mods) return false;
267265
268266 // 6. Parse optional event type after ':'.
269267 if (pos < buf.len and buf[pos] == ':') {
......@@ -735,9 +733,9 @@ test "serializeTerminalState excludes synchronized output replay" {
735733 var stream = term.vtStream();
736734 defer stream.deinit();
737735
738- stream.nextSlice("\x1b[?2004h"); // Bracketed paste
739- stream.nextSlice("\x1b[?2026h"); // Synchronized output
740- stream.nextSlice("hello");
736+ try stream.nextSlice("\x1b[?2004h"); // Bracketed paste
737+ try stream.nextSlice("\x1b[?2026h"); // Synchronized output
738+ try stream.nextSlice("hello");
741739
742740 try std.testing.expect(term.modes.get(.bracketed_paste));
743741 try std.testing.expect(term.modes.get(.synchronized_output));
......@@ -755,7 +753,7 @@ test "serializeTerminalState excludes synchronized output replay" {
755753
756754 var restored_stream = restored.vtStream();
757755 defer restored_stream.deinit();
758- restored_stream.nextSlice(output);
756+ try restored_stream.nextSlice(output);
759757
760758 try std.testing.expect(restored.modes.get(.bracketed_paste));
761759 try std.testing.expect(!restored.modes.get(.synchronized_output));