OSC 9: Finish parsing all ConEmu OSCs

Adds support for parsing OSC 9;7, 9;8, 9;9, 9;10, 9;11, 9;12
This commit is contained in:
Jeffrey C. Ollie
2026-01-21 13:32:03 -06:00
parent 2e1b501d25
commit 9ee27d2697
3 changed files with 430 additions and 20 deletions

View File

@@ -193,6 +193,28 @@ pub const Command = union(Key) {
/// ConEmu GUI macro (OSC 9;6)
conemu_guimacro: [:0]const u8,
/// ConEmu run process (OSC 9;7)
conemu_run_process: [:0]const u8,
/// ConEmu output environment variable (OSC 9;8)
conemu_output_environment_variable: [:0]const u8,
/// ConEmu XTerm keyboard and output emulation (OSC 9;10)
/// https://conemu.github.io/en/TerminalModes.html
conemu_xterm_emulation: struct {
/// null => do not change
/// false => turn off
/// true => turn on
keyboard: ?bool,
/// null => do not change
/// false => turn off
/// true => turn on
output: ?bool,
},
/// ConEmu comment (OSC 9;11)
conemu_comment: [:0]const u8,
/// Kitty text sizing protocol (OSC 66)
kitty_text_sizing: parsers.kitty_text_sizing.OSC,
@@ -221,6 +243,10 @@ pub const Command = union(Key) {
"conemu_progress_report",
"conemu_wait_input",
"conemu_guimacro",
"conemu_run_process",
"conemu_output_environment_variable",
"conemu_xterm_emulation",
"conemu_comment",
"kitty_text_sizing",
},
);
@@ -424,11 +450,15 @@ pub const Parser = struct {
.clipboard_contents,
.color_operation,
.conemu_change_tab_title,
.conemu_comment,
.conemu_guimacro,
.conemu_output_environment_variable,
.conemu_progress_report,
.conemu_run_process,
.conemu_show_message_box,
.conemu_sleep,
.conemu_wait_input,
.conemu_xterm_emulation,
.end_of_command,
.end_of_input,
.hyperlink_end,

View File

@@ -16,11 +16,11 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
var data = writer.buffered();
if (data.len == 0) break :conemu;
switch (data[0]) {
// Check for OSC 9;1 9;10 9;12
// Check for OSC 9;1 9;10 9;11 9;12
'1' => {
if (data.len < 2) break :conemu;
switch (data[1]) {
// OSC 9;1
// OSC 9;1 sleep
';' => {
parser.command = .{
.conemu_sleep = .{
@@ -29,12 +29,74 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
};
return &parser.command;
},
// OSC 9;10
// OSC 9;10 xterm keyboard and output emulation
'0' => {
parser.state = .invalid;
return null;
if (data.len == 2) {
parser.command = .{
.conemu_xterm_emulation = .{
.keyboard = true,
.output = true,
},
};
return &parser.command;
}
if (data.len < 4) break :conemu;
if (data[2] != ';') break :conemu;
switch (data[3]) {
'0' => {
parser.command = .{
.conemu_xterm_emulation = .{
.keyboard = false,
.output = false,
},
};
return &parser.command;
},
'1' => {
parser.command = .{
.conemu_xterm_emulation = .{
.keyboard = true,
.output = true,
},
};
return &parser.command;
},
'2' => {
parser.command = .{
.conemu_xterm_emulation = .{
.keyboard = null,
.output = false,
},
};
return &parser.command;
},
'3' => {
parser.command = .{
.conemu_xterm_emulation = .{
.keyboard = null,
.output = true,
},
};
return &parser.command;
},
else => break :conemu,
}
},
// OSC 9;12
// OSC 9;11 comment
'1' => {
if (data.len < 3) break :conemu;
if (data[2] != ';') break :conemu;
writer.writeByte(0) catch {
parser.state = .invalid;
return null;
};
data = writer.buffered();
parser.command = .{
.conemu_comment = data[3 .. data.len - 1 :0],
};
return &parser.command;
},
// OSC 9;12 mark prompt start
'2' => {
parser.command = .{
.prompt_start = .{},
@@ -44,7 +106,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
else => break :conemu,
}
},
// OSC 9;2
// OSC 9;2 show message box
'2' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
@@ -58,7 +120,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
};
return &parser.command;
},
// OSC 9;3
// OSC 9;3 change tab title
'3' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
@@ -80,7 +142,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
};
return &parser.command;
},
// OSC 9;4
// OSC 9;4 progress report
'4' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
@@ -141,12 +203,12 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
}
return &parser.command;
},
// OSC 9;5
// OSC 9;5 wait for input
'5' => {
parser.command = .conemu_wait_input;
return &parser.command;
},
// OSC 9;6
// OSC 9;6 guimacro
'6' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
@@ -160,26 +222,49 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
};
return &parser.command;
},
// OSC 9;7
// OSC 9;7 run process
'7' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
parser.state = .invalid;
return null;
writer.writeByte(0) catch {
parser.state = .invalid;
return null;
};
data = writer.buffered();
parser.command = .{
.conemu_run_process = data[2 .. data.len - 1 :0],
};
return &parser.command;
},
// OSC 9;8
// OSC 9;8 output environment variable
'8' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
parser.state = .invalid;
return null;
writer.writeByte(0) catch {
parser.state = .invalid;
return null;
};
data = writer.buffered();
parser.command = .{
.conemu_output_environment_variable = data[2 .. data.len - 1 :0],
};
return &parser.command;
},
// OSC 9;9
// OSC 9;9 current working directory
'9' => {
if (data.len < 2) break :conemu;
if (data[1] != ';') break :conemu;
parser.state = .invalid;
return null;
writer.writeByte(0) catch {
parser.state = .invalid;
return null;
};
data = writer.buffered();
parser.command = .{
.report_pwd = .{
.value = data[2 .. data.len - 1 :0],
},
};
return &parser.command;
},
else => break :conemu,
}
@@ -764,3 +849,294 @@ test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("6", cmd.show_desktop_notification.body);
}
test "OSC: 9;7: ConEmu run process 1" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;7;ab";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_run_process);
try testing.expectEqualStrings("ab", cmd.conemu_run_process);
}
test "OSC: 9;7: ConEmu run process 2" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;7;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_run_process);
try testing.expectEqualStrings("", cmd.conemu_run_process);
}
test "OSC: 9;7: ConEmu run process incomplete -> desktop notification" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;7";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("7", cmd.show_desktop_notification.body);
}
test "OSC: 9;8: ConEmu output environment variable 1" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;8;ab";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_output_environment_variable);
try testing.expectEqualStrings("ab", cmd.conemu_output_environment_variable);
}
test "OSC: 9;8: ConEmu output environment variable 2" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;8;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_output_environment_variable);
try testing.expectEqualStrings("", cmd.conemu_output_environment_variable);
}
test "OSC: 9;8: ConEmu output environment variable incomplete -> desktop notification" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;8";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("8", cmd.show_desktop_notification.body);
}
test "OSC: 9;9: ConEmu set current working directory" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;9;ab";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .report_pwd);
try testing.expectEqualStrings("ab", cmd.report_pwd.value);
}
test "OSC: 9;9: ConEmu set current working directory incomplete -> desktop notification" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;9";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("9", cmd.show_desktop_notification.body);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 1" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_xterm_emulation);
try testing.expect(cmd.conemu_xterm_emulation.keyboard != null);
try testing.expect(cmd.conemu_xterm_emulation.keyboard.? == true);
try testing.expect(cmd.conemu_xterm_emulation.output != null);
try testing.expect(cmd.conemu_xterm_emulation.output.? == true);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 2" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;0";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_xterm_emulation);
try testing.expect(cmd.conemu_xterm_emulation.keyboard != null);
try testing.expect(cmd.conemu_xterm_emulation.keyboard.? == false);
try testing.expect(cmd.conemu_xterm_emulation.output != null);
try testing.expect(cmd.conemu_xterm_emulation.output.? == false);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 3" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;1";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_xterm_emulation);
try testing.expect(cmd.conemu_xterm_emulation.keyboard != null);
try testing.expect(cmd.conemu_xterm_emulation.keyboard.? == true);
try testing.expect(cmd.conemu_xterm_emulation.output != null);
try testing.expect(cmd.conemu_xterm_emulation.output.? == true);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 4" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;2";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_xterm_emulation);
try testing.expect(cmd.conemu_xterm_emulation.keyboard == null);
try testing.expect(cmd.conemu_xterm_emulation.output != null);
try testing.expect(cmd.conemu_xterm_emulation.output.? == false);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 5" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;3";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_xterm_emulation);
try testing.expect(cmd.conemu_xterm_emulation.keyboard == null);
try testing.expect(cmd.conemu_xterm_emulation.output != null);
try testing.expect(cmd.conemu_xterm_emulation.output.? == true);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 6" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;4";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("10;4", cmd.show_desktop_notification.body);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 7" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("10;", cmd.show_desktop_notification.body);
}
test "OSC: 9;10: ConEmu xterm keyboard and output emulation 8" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;10;abc";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("10;abc", cmd.show_desktop_notification.body);
}
test "OSC: 9;11: ConEmu comment" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;11;ab";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_comment);
try testing.expectEqualStrings("ab", cmd.conemu_comment);
}
test "OSC: 9;11: ConEmu comment incomplete -> desktop notification" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;11";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .show_desktop_notification);
try testing.expectEqualStrings("11", cmd.show_desktop_notification.body);
}
test "OSC: 9;12: ConEmu mark prompt start 1" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;12";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .prompt_start);
}
test "OSC: 9;12: ConEmu mark prompt start 2" {
const testing = std.testing;
var p: Parser = .init(testing.allocator);
defer p.deinit();
const input = "9;12;abc";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .prompt_start);
}

View File

@@ -2107,6 +2107,10 @@ pub fn Stream(comptime Handler: type) type {
.conemu_change_tab_title,
.conemu_wait_input,
.conemu_guimacro,
.conemu_comment,
.conemu_xterm_emulation,
.conemu_output_environment_variable,
.conemu_run_process,
.kitty_text_sizing,
=> {
log.debug("unimplemented OSC callback: {}", .{cmd});