- commit
- 08a06c0
- parent
- d0e3ed1
- author
- Eric Bower
- date
- 2026-04-04 10:22:43 -0400 EDT
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
1@@ -64,8 +64,22 @@ pub fn build(b: *std.Build) void {
2 // Test
3 {
4 const test_step = b.step("test", "Run unit tests");
5+ const test_module = b.addModule("test", .{
6+ .root_source_file = b.path("src/test.zig"),
7+ .target = target,
8+ .optimize = optimize,
9+ });
10+ if (b.lazyDependency("ghostty", .{
11+ .target = target,
12+ .optimize = optimize,
13+ })) |dep| {
14+ test_module.addImport(
15+ "ghostty-vt",
16+ dep.module("ghostty-vt"),
17+ );
18+ }
19 const exe_unit_tests = b.addTest(.{
20- .root_module = exe_mod,
21+ .root_module = test_module,
22 });
23 const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
24 test_step.dependOn(&run_exe_unit_tests.step);
+5,
-0
1@@ -0,0 +1,5 @@
2+comptime {
3+ _ = @import("main.zig");
4+ _ = @import("util.zig");
5+ _ = @import("socket.zig");
6+}
+10,
-12
1@@ -220,24 +220,22 @@ pub fn findTaskExitMarker(output: []const u8) ?u8 {
2 /// and alternate key sub-fields from the kitty protocol's progressive
3 /// enhancement flags.
4 pub fn isKittyCtrlBackslash(buf: []const u8) bool {
5- return isKittyCtrlKey(buf, 92);
6+ return detectCsiKeyPress(buf, 92, 0b100);
7 }
8
9-fn isKittyCtrlKey(buf: []const u8, key_code: u32) bool {
10+fn detectCsiKeyPress(buf: []const u8, expected_key: u32, expected_mods: u32) bool {
11 // Scan for any CSI u sequence encoding the given Ctrl+key in the buffer.
12 // The sequence can appear at any offset (e.g. preceded by other input).
13 var i: usize = 0;
14 while (i + 2 < buf.len) : (i += 1) {
15 if (buf[i] == 0x1b and buf[i + 1] == '[') {
16- if (parseKittyCtrlKey(buf[i + 2 ..], key_code)) return true;
17+ if (isKeyPressed(buf[i + 2 ..], expected_key, expected_mods)) return true;
18 }
19 }
20 return false;
21 }
22
23-/// Parse a CSI u sequence (after the `\x1b[` prefix) and return true if it
24-/// encodes a Ctrl+key press or repeat event for the given key code.
25-fn parseKittyCtrlKey(buf: []const u8, expected_key: u32) bool {
26+fn isKeyPressed(buf: []const u8, expected_key: u32, expected_mods: u32) bool {
27 var pos: usize = 0;
28
29 // 1. Parse key code.
30@@ -259,11 +257,11 @@ fn parseKittyCtrlKey(buf: []const u8, expected_key: u32) bool {
31 if (mod_encoded < 1) return false;
32 const mod_raw = mod_encoded - 1;
33
34- // 5. Ctrl must be the only intentional modifier. Lock modifiers
35+ // 5. Only accept intentional modifiers. Lock modifiers
36 // (caps_lock=0b1000000, num_lock=0b10000000) are tolerated because
37 // they are ambient state, not deliberate key combinations.
38 const intentional_mods = mod_raw & 0b00111111;
39- if (intentional_mods != 0b100) return false;
40+ if (intentional_mods != expected_mods) return false;
41
42 // 6. Parse optional event type after ':'.
43 if (pos < buf.len and buf[pos] == ':') {
44@@ -735,9 +733,9 @@ test "serializeTerminalState excludes synchronized output replay" {
45 var stream = term.vtStream();
46 defer stream.deinit();
47
48- stream.nextSlice("\x1b[?2004h"); // Bracketed paste
49- stream.nextSlice("\x1b[?2026h"); // Synchronized output
50- stream.nextSlice("hello");
51+ try stream.nextSlice("\x1b[?2004h"); // Bracketed paste
52+ try stream.nextSlice("\x1b[?2026h"); // Synchronized output
53+ try stream.nextSlice("hello");
54
55 try std.testing.expect(term.modes.get(.bracketed_paste));
56 try std.testing.expect(term.modes.get(.synchronized_output));
57@@ -755,7 +753,7 @@ test "serializeTerminalState excludes synchronized output replay" {
58
59 var restored_stream = restored.vtStream();
60 defer restored_stream.deinit();
61- restored_stream.nextSlice(output);
62+ try restored_stream.nextSlice(output);
63
64 try std.testing.expect(restored.modes.get(.bracketed_paste));
65 try std.testing.expect(!restored.modes.get(.synchronized_output));