From b52e76334ee634df243750065d0dba66ca657a52 Mon Sep 17 00:00:00 2001 From: Damien MEHALA Date: Wed, 1 Jan 2025 17:02:06 +0100 Subject: [PATCH 1/3] feat: parse ConEmu OSC9;1 --- src/terminal/osc.zig | 84 ++++++++++++++++++++++++++++++++++++++++- src/terminal/stream.zig | 2 +- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 19d8212a05..da081daa94 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -67,7 +67,7 @@ pub const Command = union(enum) { /// End of current command. /// - /// The exit-code need not be specified if if there are no options, + /// The exit-code need not be specified if there are no options, /// or if the command was cancelled (no OSC "133;C"), such as by typing /// an interrupt/cancel character (typically ctrl-C) during line-editing. /// Otherwise, it must be an integer code, where 0 means the command @@ -158,6 +158,9 @@ pub const Command = union(enum) { /// End a hyperlink (OSC 8) hyperlink_end: void, + /// Sleep (OSC 9;1) in ms + sleep: u16, + /// Set progress state (OSC 9;4) progress: struct { state: ProgressState, @@ -353,6 +356,8 @@ pub const Parser = struct { osc_9, // ConEmu specific substates + conemu_sleep, + conemu_sleep_value, conemu_progress_prestate, conemu_progress_state, conemu_progress_prevalue, @@ -777,6 +782,9 @@ pub const Parser = struct { }, .osc_9 => switch (c) { + '1' => { + self.state = .conemu_sleep; + }, '4' => { self.state = .conemu_progress_prestate; }, @@ -788,6 +796,21 @@ pub const Parser = struct { else => self.showDesktopNotification(), }, + .conemu_sleep => switch (c) { + ';' => { + self.command = .{ .sleep = 100 }; + self.buf_start = self.buf_idx; + self.complete = true; + self.state = .conemu_sleep_value; + }, + else => self.state = .invalid, + }, + + .conemu_sleep_value => switch (c) { + '0'...'9' => {}, + else => self.state = .invalid, + }, + .conemu_progress_prestate => switch (c) { ';' => { self.command = .{ .progress = .{ @@ -1147,6 +1170,22 @@ pub const Parser = struct { self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx]; } + fn endConEmuSleepValue(self: *Parser) void { + switch (self.command) { + .sleep => |*v| { + const str = self.buf[self.buf_start..self.buf_idx]; + if (str.len != 0) { + if (std.fmt.parseUnsigned(u16, str, 10)) |num| { + v.* = @min(num, 10000); + } else |_| { + v.* = 10000; + } + } + }, + else => {}, + } + } + fn endKittyColorProtocolOption(self: *Parser, kind: enum { key_only, key_and_value }, final: bool) void { if (self.temp_state.key.len == 0) { log.warn("zero length key in kitty color protocol", .{}); @@ -1225,6 +1264,7 @@ pub const Parser = struct { .semantic_option_value => self.endSemanticOptionValue(), .hyperlink_uri => self.endHyperlink(), .string => self.endString(), + .conemu_sleep_value => self.endConEmuSleepValue(), .allocable_string => self.endAllocableString(), .kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true), .kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true), @@ -1634,6 +1674,48 @@ test "OSC: set palette color" { try testing.expectEqualStrings(cmd.set_color.value, "rgb:aa/bb/cc"); } +test "OSC: conemu sleep" { + const testing = std.testing; + + var p: Parser = .{}; + + const input = "9;1;420"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .sleep); + try testing.expectEqual(420, cmd.sleep); +} + +test "OSC: conemu sleep with no value default to 100ms" { + const testing = std.testing; + + var p: Parser = .{}; + + const input = "9;1;"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .sleep); + try testing.expectEqual(100, cmd.sleep); +} + +test "OSC: conemu sleep cannot exceed 10000ms" { + const testing = std.testing; + + var p: Parser = .{}; + + const input = "9;1;12345"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .sleep); + try testing.expectEqual(10000, cmd.sleep); +} + test "OSC: show desktop notification" { const testing = std.testing; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 59a8e704db..8772050a9f 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -1455,7 +1455,7 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, - .progress => { + .progress, .sleep => { log.warn("unimplemented OSC callback: {}", .{cmd}); }, } From c98d207eb98b1a0d822e148c79792f1b17c3a98a Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 2 Jan 2025 00:13:55 +0100 Subject: [PATCH 2/3] code review - Add test with invalid value. - Fix inspector compilation. --- src/inspector/termio.zig | 2 +- src/terminal/osc.zig | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/inspector/termio.zig b/src/inspector/termio.zig index 78b35e19b9..18a6928228 100644 --- a/src/inspector/termio.zig +++ b/src/inspector/termio.zig @@ -265,7 +265,7 @@ pub const VTEvent = struct { ), else => switch (Value) { - u8 => try md.put( + u8, u16 => try md.put( key, try std.fmt.allocPrintZ(alloc, "{}", .{value}), ), diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index da081daa94..300e1e5b5a 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -158,8 +158,10 @@ pub const Command = union(enum) { /// End a hyperlink (OSC 8) hyperlink_end: void, - /// Sleep (OSC 9;1) in ms - sleep: u16, + /// Sleep (OSC 9;1) + sleep: struct { + duration_ms: u16, + }, /// Set progress state (OSC 9;4) progress: struct { @@ -798,7 +800,7 @@ pub const Parser = struct { .conemu_sleep => switch (c) { ';' => { - self.command = .{ .sleep = 100 }; + self.command = .{ .sleep = .{ .duration_ms = 100 } }; self.buf_start = self.buf_idx; self.complete = true; self.state = .conemu_sleep_value; @@ -1176,9 +1178,9 @@ pub const Parser = struct { const str = self.buf[self.buf_start..self.buf_idx]; if (str.len != 0) { if (std.fmt.parseUnsigned(u16, str, 10)) |num| { - v.* = @min(num, 10000); + v.duration_ms = @min(num, 10_000); } else |_| { - v.* = 10000; + v.duration_ms = 10_000; } } }, @@ -1685,7 +1687,7 @@ test "OSC: conemu sleep" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .sleep); - try testing.expectEqual(420, cmd.sleep); + try testing.expectEqual(420, cmd.sleep.duration_ms); } test "OSC: conemu sleep with no value default to 100ms" { @@ -1699,7 +1701,7 @@ test "OSC: conemu sleep with no value default to 100ms" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .sleep); - try testing.expectEqual(100, cmd.sleep); + try testing.expectEqual(100, cmd.sleep.duration_ms); } test "OSC: conemu sleep cannot exceed 10000ms" { @@ -1713,7 +1715,20 @@ test "OSC: conemu sleep cannot exceed 10000ms" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .sleep); - try testing.expectEqual(10000, cmd.sleep); + try testing.expectEqual(10000, cmd.sleep.duration_ms); +} + +test "OSC: conemu sleep invalid input" { + const testing = std.testing; + + var p: Parser = .{}; + + const input = "9;1;foo"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b'); + + try testing.expect(cmd == null); } test "OSC: show desktop notification" { From 9d9fa60ece3736001be39ec2ac80b0e6c4b44f17 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 2 Jan 2025 23:57:53 +0100 Subject: [PATCH 3/3] code review - Default to 100 if the value can't be parsed as an integer or is missing entirely. --- src/terminal/osc.zig | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 300e1e5b5a..1a336a604e 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -809,8 +809,7 @@ pub const Parser = struct { }, .conemu_sleep_value => switch (c) { - '0'...'9' => {}, - else => self.state = .invalid, + else => self.complete = true, }, .conemu_progress_prestate => switch (c) { @@ -1174,14 +1173,14 @@ pub const Parser = struct { fn endConEmuSleepValue(self: *Parser) void { switch (self.command) { - .sleep => |*v| { + .sleep => |*v| v.duration_ms = value: { const str = self.buf[self.buf_start..self.buf_idx]; - if (str.len != 0) { - if (std.fmt.parseUnsigned(u16, str, 10)) |num| { - v.duration_ms = @min(num, 10_000); - } else |_| { - v.duration_ms = 10_000; - } + if (str.len == 0) break :value 100; + + if (std.fmt.parseUnsigned(u16, str, 10)) |num| { + break :value @min(num, 10_000); + } else |_| { + break :value 100; } }, else => {}, @@ -1726,9 +1725,10 @@ test "OSC: conemu sleep invalid input" { const input = "9;1;foo"; for (input) |ch| p.next(ch); - const cmd = p.end('\x1b'); + const cmd = p.end('\x1b').?; - try testing.expect(cmd == null); + try testing.expect(cmd == .sleep); + try testing.expectEqual(100, cmd.sleep.duration_ms); } test "OSC: show desktop notification" {