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 { | |
| 64 | 64 | // Test | |
| 65 | 65 | { | |
| 66 | 66 | 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 | + | } | |
| 67 | 81 | const exe_unit_tests = b.addTest(.{ | |
| 68 | - | .root_module = exe_mod, | |
| 82 | + | .root_module = test_module, | |
| 69 | 83 | }); | |
| 70 | 84 | const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); | |
| 71 | 85 | 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 { | |
| 220 | 220 | /// and alternate key sub-fields from the kitty protocol's progressive | |
| 221 | 221 | /// enhancement flags. | |
| 222 | 222 | pub fn isKittyCtrlBackslash(buf: []const u8) bool { | |
| 223 | - | return isKittyCtrlKey(buf, 92); | |
| 223 | + | return detectCsiKeyPress(buf, 92, 0b100); | |
| 224 | 224 | } | |
| 225 | 225 | ||
| 226 | - | fn isKittyCtrlKey(buf: []const u8, key_code: u32) bool { | |
| 226 | + | fn detectCsiKeyPress(buf: []const u8, expected_key: u32, expected_mods: u32) bool { | |
| 227 | 227 | // Scan for any CSI u sequence encoding the given Ctrl+key in the buffer. | |
| 228 | 228 | // The sequence can appear at any offset (e.g. preceded by other input). | |
| 229 | 229 | var i: usize = 0; | |
| 230 | 230 | while (i + 2 < buf.len) : (i += 1) { | |
| 231 | 231 | 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; | |
| 233 | 233 | } | |
| 234 | 234 | } | |
| 235 | 235 | return false; | |
| 236 | 236 | } | |
| 237 | 237 | ||
| 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 { | |
| 241 | 239 | var pos: usize = 0; | |
| 242 | 240 | ||
| 243 | 241 | // 1. Parse key code. |
| ... | ... | @@ -259,11 +257,11 @@ fn parseKittyCtrlKey(buf: []const u8, expected_key: u32) bool { | |
| 259 | 257 | if (mod_encoded < 1) return false; | |
| 260 | 258 | const mod_raw = mod_encoded - 1; | |
| 261 | 259 | ||
| 262 | - | // 5. Ctrl must be the only intentional modifier. Lock modifiers | |
| 260 | + | // 5. Only accept intentional modifiers. Lock modifiers | |
| 263 | 261 | // (caps_lock=0b1000000, num_lock=0b10000000) are tolerated because | |
| 264 | 262 | // they are ambient state, not deliberate key combinations. | |
| 265 | 263 | const intentional_mods = mod_raw & 0b00111111; | |
| 266 | - | if (intentional_mods != 0b100) return false; | |
| 264 | + | if (intentional_mods != expected_mods) return false; | |
| 267 | 265 | ||
| 268 | 266 | // 6. Parse optional event type after ':'. | |
| 269 | 267 | if (pos < buf.len and buf[pos] == ':') { |
| ... | ... | @@ -735,9 +733,9 @@ test "serializeTerminalState excludes synchronized output replay" { | |
| 735 | 733 | var stream = term.vtStream(); | |
| 736 | 734 | defer stream.deinit(); | |
| 737 | 735 | ||
| 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"); | |
| 741 | 739 | ||
| 742 | 740 | try std.testing.expect(term.modes.get(.bracketed_paste)); | |
| 743 | 741 | try std.testing.expect(term.modes.get(.synchronized_output)); |
| ... | ... | @@ -755,7 +753,7 @@ test "serializeTerminalState excludes synchronized output replay" { | |
| 755 | 753 | ||
| 756 | 754 | var restored_stream = restored.vtStream(); | |
| 757 | 755 | defer restored_stream.deinit(); | |
| 758 | - | restored_stream.nextSlice(output); | |
| 756 | + | try restored_stream.nextSlice(output); | |
| 759 | 757 | ||
| 760 | 758 | try std.testing.expect(restored.modes.get(.bracketed_paste)); | |
| 761 | 759 | try std.testing.expect(!restored.modes.get(.synchronized_output)); |