terminal: ConEmu OSC9 parsing is more robust and correct

Related to #4485

This commit matches ConEmu's parsing logic[^1] more faithfully. For any
substate that requires a progress, ConEmu parses so long as there is a
number and then just ignores the rest.

For substates that don't require a progress, ConEmu literally ignores
everything after the state.

Tests cover both.

[^1]: 740b09c363/src/ConEmuCD/ConAnsiImpl.cpp (L2264)
This commit is contained in:
Mitchell Hashimoto
2025-01-06 15:39:21 -08:00
parent 037de64ea2
commit 7ae94e145d

View File

@ -274,6 +274,7 @@ pub const Parser = struct {
pub const State = enum { pub const State = enum {
empty, empty,
invalid, invalid,
swallow,
// Command prefixes. We could just accumulate and compare (mem.eql) // Command prefixes. We could just accumulate and compare (mem.eql)
// but the state space is small enough that we just build it up this way. // but the state space is small enough that we just build it up this way.
@ -451,6 +452,8 @@ pub const Parser = struct {
else => self.state = .invalid, else => self.state = .invalid,
}, },
.swallow => {},
.@"0" => switch (c) { .@"0" => switch (c) {
';' => { ';' => {
self.command = .{ .change_window_title = undefined }; self.command = .{ .change_window_title = undefined };
@ -871,7 +874,7 @@ pub const Parser = struct {
.conemu_progress_state => switch (c) { .conemu_progress_state => switch (c) {
'0' => { '0' => {
self.command.progress.state = .remove; self.command.progress.state = .remove;
self.state = .conemu_progress_prevalue; self.state = .swallow;
self.complete = true; self.complete = true;
}, },
'1' => { '1' => {
@ -887,7 +890,7 @@ pub const Parser = struct {
'3' => { '3' => {
self.command.progress.state = .indeterminate; self.command.progress.state = .indeterminate;
self.complete = true; self.complete = true;
self.state = .conemu_progress_prevalue; self.state = .swallow;
}, },
'4' => { '4' => {
self.command.progress.state = .pause; self.command.progress.state = .pause;
@ -934,7 +937,10 @@ pub const Parser = struct {
} }
}, },
else => self.showDesktopNotification(), else => {
self.state = .swallow;
self.complete = true;
},
}, },
.query_fg_color => switch (c) { .query_fg_color => switch (c) {
@ -1965,18 +1971,18 @@ test "OSC: OSC9 progress set double digit" {
try testing.expect(cmd.progress.progress == 94); try testing.expect(cmd.progress.progress == 94);
} }
test "OSC: OSC9 progress set extra semicolon triggers desktop notification" { test "OSC: OSC9 progress set extra semicolon ignored" {
const testing = std.testing; const testing = std.testing;
var p: Parser = .{}; var p: Parser = .{};
const input = "9;4;1;100;"; const input = "9;4;1;100";
for (input) |ch| p.next(ch); for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?; const cmd = p.end('\x1b').?;
try testing.expect(cmd == .show_desktop_notification); try testing.expect(cmd == .progress);
try testing.expectEqualStrings(cmd.show_desktop_notification.title, ""); try testing.expect(cmd.progress.state == .set);
try testing.expectEqualStrings(cmd.show_desktop_notification.body, "4;1;100;"); try testing.expect(cmd.progress.progress == 100);
} }
test "OSC: OSC9 progress remove with no progress" { test "OSC: OSC9 progress remove with no progress" {
@ -1993,6 +1999,20 @@ test "OSC: OSC9 progress remove with no progress" {
try testing.expect(cmd.progress.progress == null); try testing.expect(cmd.progress.progress == null);
} }
test "OSC: OSC9 progress remove with double semicolon" {
const testing = std.testing;
var p: Parser = .{};
const input = "9;4;0;;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
try testing.expect(cmd == .progress);
try testing.expect(cmd.progress.state == .remove);
try testing.expect(cmd.progress.progress == null);
}
test "OSC: OSC9 progress remove ignores progress" { test "OSC: OSC9 progress remove ignores progress" {
const testing = std.testing; const testing = std.testing;
@ -2016,9 +2036,8 @@ test "OSC: OSC9 progress remove extra semicolon" {
for (input) |ch| p.next(ch); for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?; const cmd = p.end('\x1b').?;
try testing.expect(cmd == .show_desktop_notification); try testing.expect(cmd == .progress);
try testing.expectEqualStrings(cmd.show_desktop_notification.title, ""); try testing.expect(cmd.progress.state == .remove);
try testing.expectEqualStrings(cmd.show_desktop_notification.body, "4;0;100;");
} }
test "OSC: OSC9 progress error" { test "OSC: OSC9 progress error" {