Merge branch 'main' into jacob/uucode

This commit is contained in:
Mitchell Hashimoto
2025-09-30 06:52:07 -07:00
committed by GitHub
55 changed files with 2365 additions and 1182 deletions

View File

@@ -274,7 +274,7 @@ pub fn next(self: *Parser, c: u8) [3]?Action {
// Exit depends on current state
if (self.state == next_state) null else switch (self.state) {
.osc_string => if (self.osc_parser.end(c)) |cmd|
Action{ .osc_dispatch = cmd }
Action{ .osc_dispatch = cmd.* }
else
null,
.dcs_passthrough => Action{ .dcs_unhook = {} },

View File

@@ -1,5 +1,6 @@
const std = @import("std");
/// Options set by Zig build.zig and exposed via `terminal_options`.
pub const Options = struct {
/// The target artifact to build. This will gate some functionality.
artifact: Artifact,
@@ -23,6 +24,10 @@ pub const Options = struct {
/// generally be disabled in production builds.
slow_runtime_safety: bool,
/// Force C ABI mode on or off. If not set, then it will be set based on
/// Options.
c_abi: bool,
/// Add the required build options for the terminal module.
pub fn add(
self: Options,
@@ -31,6 +36,7 @@ pub const Options = struct {
) void {
const opts = b.addOptions();
opts.addOption(Artifact, "artifact", self.artifact);
opts.addOption(bool, "c_abi", self.c_abi);
opts.addOption(bool, "oniguruma", self.oniguruma);
opts.addOption(bool, "simd", self.simd);
opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety);

17
src/terminal/c/main.zig Normal file
View File

@@ -0,0 +1,17 @@
pub const osc = @import("osc.zig");
// The full C API, unexported.
pub const osc_new = osc.new;
pub const osc_free = osc.free;
pub const osc_reset = osc.reset;
pub const osc_next = osc.next;
pub const osc_end = osc.end;
pub const osc_command_type = osc.commandType;
pub const osc_command_data = osc.commandData;
test {
_ = osc;
// We want to make sure we run the tests for the C allocator interface.
_ = @import("../../lib/allocator.zig");
}

132
src/terminal/c/osc.zig Normal file
View File

@@ -0,0 +1,132 @@
const std = @import("std");
const assert = std.debug.assert;
const builtin = @import("builtin");
const lib_alloc = @import("../../lib/allocator.zig");
const CAllocator = lib_alloc.Allocator;
const osc = @import("../osc.zig");
const Result = @import("result.zig").Result;
/// C: GhosttyOscParser
pub const Parser = ?*osc.Parser;
/// C: GhosttyOscCommand
pub const Command = ?*osc.Command;
pub fn new(
alloc_: ?*const CAllocator,
result: *Parser,
) callconv(.c) Result {
const alloc = lib_alloc.default(alloc_);
const ptr = alloc.create(osc.Parser) catch
return .out_of_memory;
ptr.* = .initAlloc(alloc);
result.* = ptr;
return .success;
}
pub fn free(parser_: Parser) callconv(.c) void {
// C-built parsers always have an associated allocator.
const parser = parser_ orelse return;
const alloc = parser.alloc.?;
parser.deinit();
alloc.destroy(parser);
}
pub fn reset(parser_: Parser) callconv(.c) void {
parser_.?.reset();
}
pub fn next(parser_: Parser, byte: u8) callconv(.c) void {
parser_.?.next(byte);
}
pub fn end(parser_: Parser, terminator: u8) callconv(.c) Command {
return parser_.?.end(terminator);
}
pub fn commandType(command_: Command) callconv(.c) osc.Command.Key {
const command = command_ orelse return .invalid;
return command.*;
}
/// C: GhosttyOscCommandData
pub const CommandData = enum(c_int) {
invalid = 0,
change_window_title_str = 1,
/// Output type expected for querying the data of the given kind.
pub fn OutType(comptime self: CommandData) type {
return switch (self) {
.invalid => void,
.change_window_title_str => [*:0]const u8,
};
}
};
pub fn commandData(
command_: Command,
data: CommandData,
out: ?*anyopaque,
) callconv(.c) bool {
return switch (data) {
inline else => |comptime_data| commandDataTyped(
command_,
comptime_data,
@ptrCast(@alignCast(out)),
),
};
}
fn commandDataTyped(
command_: Command,
comptime data: CommandData,
out: *data.OutType(),
) bool {
const command = command_.?;
switch (data) {
.invalid => return false,
.change_window_title_str => switch (command.*) {
.change_window_title => |v| out.* = v.ptr,
else => return false,
},
}
return true;
}
test "alloc" {
const testing = std.testing;
var p: Parser = undefined;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&p,
));
free(p);
}
test "command type null" {
const testing = std.testing;
try testing.expectEqual(.invalid, commandType(null));
}
test "change window title" {
const testing = std.testing;
var p: Parser = undefined;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&p,
));
defer free(p);
// Parse it
next(p, '0');
next(p, ';');
next(p, 'a');
const cmd = end(p, 0);
try testing.expectEqual(.change_window_title, commandType(cmd));
// Extract the title
var title: [*:0]const u8 = undefined;
try testing.expect(commandData(cmd, .change_window_title_str, @ptrCast(&title)));
try testing.expectEqualStrings("a", std.mem.span(title));
}

View File

@@ -0,0 +1,5 @@
/// C: GhosttyResult
pub const Result = enum(c_int) {
success = 0,
out_of_memory = -1,
};

View File

@@ -1,49 +0,0 @@
const std = @import("std");
const assert = std.debug.assert;
const builtin = @import("builtin");
const lib_alloc = @import("../lib/allocator.zig");
const CAllocator = lib_alloc.Allocator;
const osc = @import("osc.zig");
/// C: GhosttyOscParser
pub const OscParser = ?*osc.Parser;
/// C: GhosttyResult
pub const Result = enum(c_int) {
success = 0,
out_of_memory = -1,
};
pub fn osc_new(
alloc_: ?*const CAllocator,
result: *OscParser,
) callconv(.c) Result {
const alloc = lib_alloc.default(alloc_);
const ptr = alloc.create(osc.Parser) catch
return .out_of_memory;
ptr.* = .initAlloc(alloc);
result.* = ptr;
return .success;
}
pub fn osc_free(parser_: OscParser) callconv(.c) void {
// C-built parsers always have an associated allocator.
const parser = parser_ orelse return;
const alloc = parser.alloc.?;
parser.deinit();
alloc.destroy(parser);
}
test {
_ = lib_alloc;
}
test "osc" {
const testing = std.testing;
var p: OscParser = undefined;
try testing.expectEqual(Result.success, osc_new(
&lib_alloc.test_allocator,
&p,
));
osc_free(p);
}

View File

@@ -1,5 +1,4 @@
const builtin = @import("builtin");
const build_options = @import("terminal_options");
const charsets = @import("charsets.zig");
const sanitize = @import("sanitize.zig");
@@ -21,7 +20,7 @@ pub const page = @import("page.zig");
pub const parse_table = @import("parse_table.zig");
pub const search = @import("search.zig");
pub const size = @import("size.zig");
pub const tmux = if (build_options.tmux_control_mode) @import("tmux.zig") else struct {};
pub const tmux = if (options.tmux_control_mode) @import("tmux.zig") else struct {};
pub const x11_color = @import("x11_color.zig");
pub const Charset = charsets.Charset;
@@ -62,9 +61,11 @@ pub const Attribute = sgr.Attribute;
pub const isSafePaste = sanitize.isSafePaste;
pub const Options = @import("build_options.zig").Options;
pub const options = @import("terminal_options");
/// This is set to true when we're building the C library.
pub const is_c_lib = @import("root") == @import("../lib_vt.zig");
pub const c_api = @import("c_api.zig");
pub const c_api = if (options.c_abi) @import("c/main.zig") else void;
test {
@import("std").testing.refAllDecls(@This());

View File

@@ -7,9 +7,11 @@ const osc = @This();
const std = @import("std");
const builtin = @import("builtin");
const build_options = @import("terminal_options");
const mem = std.mem;
const assert = std.debug.assert;
const Allocator = mem.Allocator;
const LibEnum = @import("../lib/enum.zig").Enum;
const RGB = @import("color.zig").RGB;
const kitty_color = @import("kitty/color.zig");
const osc_color = @import("osc/color.zig");
@@ -17,26 +19,26 @@ pub const color = osc_color;
const log = std.log.scoped(.osc);
pub const Command = union(enum) {
pub const Command = union(Key) {
/// This generally shouldn't ever be set except as an initial zero value.
/// Ignore it.
invalid,
/// Set the window title of the terminal
///
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
/// with each code unit further encoded with two hex digits).
///
/// If title mode 2 is set or the terminal is setup for unconditional
/// utf-8 titles text is interpreted as utf-8. Else text is interpreted
/// as latin1.
change_window_title: []const u8,
change_window_title: [:0]const u8,
/// Set the icon of the terminal window. The name of the icon is not
/// well defined, so this is currently ignored by Ghostty at the time
/// of writing this. We just parse it so that we don't get parse errors
/// in the log.
change_window_icon: []const u8,
change_window_icon: [:0]const u8,
/// First do a fresh-line. Then start a new command, and enter prompt mode:
/// Subsequent text (until a OSC "133;B" or OSC "133;I" command) is a
@@ -52,7 +54,7 @@ pub const Command = union(enum) {
/// - secondary: a non-editable continuation line
/// - right: a right-aligned prompt that may need adjustment during reflow
prompt_start: struct {
aid: ?[]const u8 = null,
aid: ?[:0]const u8 = null,
kind: enum { primary, continuation, secondary, right } = .primary,
redraw: bool = true,
},
@@ -94,7 +96,7 @@ pub const Command = union(enum) {
/// contents is set on the clipboard.
clipboard_contents: struct {
kind: u8,
data: []const u8,
data: [:0]const u8,
},
/// OSC 7. Reports the current working directory of the shell. This is
@@ -104,7 +106,7 @@ pub const Command = union(enum) {
report_pwd: struct {
/// The reported pwd value. This is not checked for validity. It should
/// be a file URL but it is up to the caller to utilize this value.
value: []const u8,
value: [:0]const u8,
},
/// OSC 22. Set the mouse shape. There doesn't seem to be a standard
@@ -112,7 +114,7 @@ pub const Command = union(enum) {
/// are moving towards using the W3C CSS cursor names. For OSC parsing,
/// we just parse whatever string is given.
mouse_shape: struct {
value: []const u8,
value: [:0]const u8,
},
/// OSC color operations to set, reset, or report color settings. Some OSCs
@@ -136,14 +138,14 @@ pub const Command = union(enum) {
/// Show a desktop notification (OSC 9 or OSC 777)
show_desktop_notification: struct {
title: []const u8,
body: []const u8,
title: [:0]const u8,
body: [:0]const u8,
},
/// Start a hyperlink (OSC 8)
hyperlink_start: struct {
id: ?[]const u8 = null,
uri: []const u8,
id: ?[:0]const u8 = null,
uri: [:0]const u8,
},
/// End a hyperlink (OSC 8)
@@ -155,12 +157,12 @@ pub const Command = union(enum) {
},
/// ConEmu show GUI message box (OSC 9;2)
conemu_show_message_box: []const u8,
conemu_show_message_box: [:0]const u8,
/// ConEmu change tab title (OSC 9;3)
conemu_change_tab_title: union(enum) {
reset,
value: []const u8,
value: [:0]const u8,
},
/// ConEmu progress report (OSC 9;4)
@@ -170,7 +172,35 @@ pub const Command = union(enum) {
conemu_wait_input,
/// ConEmu GUI macro (OSC 9;6)
conemu_guimacro: []const u8,
conemu_guimacro: [:0]const u8,
pub const Key = LibEnum(
if (build_options.c_abi) .c else .zig,
// NOTE: Order matters, see LibEnum documentation.
&.{
"invalid",
"change_window_title",
"change_window_icon",
"prompt_start",
"prompt_end",
"end_of_input",
"end_of_command",
"clipboard_contents",
"report_pwd",
"mouse_shape",
"color_operation",
"kitty_color_protocol",
"show_desktop_notification",
"hyperlink_start",
"hyperlink_end",
"conemu_sleep",
"conemu_show_message_box",
"conemu_change_tab_title",
"conemu_progress_report",
"conemu_wait_input",
"conemu_guimacro",
},
);
pub const ProgressReport = struct {
pub const State = enum(c_int) {
@@ -275,7 +305,7 @@ pub const Parser = struct {
/// Temporary state that is dependent on the current state.
temp_state: union {
/// Current string parameter being populated
str: *[]const u8,
str: *[:0]const u8,
/// Current numeric parameter being populated
num: u16,
@@ -431,7 +461,7 @@ pub const Parser = struct {
self.reset();
}
/// Reset the parser start.
/// Reset the parser state.
pub fn reset(self: *Parser) void {
// If the state is already empty then we do nothing because
// we may touch uninitialized memory.
@@ -468,7 +498,10 @@ pub const Parser = struct {
// If our buffer is full then we're invalid, so we set our state
// accordingly and indicate the sequence is incomplete so that we
// don't accidentally issue a command when ending.
if (self.buf_idx >= self.buf.len) {
//
// We always keep space for 1 byte at the end to null-terminate
// values.
if (self.buf_idx >= self.buf.len - 1) {
if (self.state != .invalid) {
log.warn(
"OSC sequence too long (> {d}), ignoring. state={}",
@@ -1007,7 +1040,8 @@ pub const Parser = struct {
.notification_title => switch (c) {
';' => {
self.command.show_desktop_notification.title = self.buf[self.buf_start .. self.buf_idx - 1];
self.buf[self.buf_idx - 1] = 0;
self.command.show_desktop_notification.title = self.buf[self.buf_start .. self.buf_idx - 1 :0];
self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
self.buf_start = self.buf_idx;
self.state = .string;
@@ -1376,7 +1410,8 @@ pub const Parser = struct {
fn endHyperlink(self: *Parser) void {
switch (self.command) {
.hyperlink_start => |*v| {
const value = self.buf[self.buf_start..self.buf_idx];
self.buf[self.buf_idx] = 0;
const value = self.buf[self.buf_start..self.buf_idx :0];
if (v.id == null and value.len == 0) {
self.command = .{ .hyperlink_end = {} };
return;
@@ -1390,10 +1425,12 @@ pub const Parser = struct {
}
fn endHyperlinkOptionValue(self: *Parser) void {
const value = if (self.buf_start == self.buf_idx)
const value: [:0]const u8 = if (self.buf_start == self.buf_idx)
""
else
self.buf[self.buf_start .. self.buf_idx - 1];
else buf: {
self.buf[self.buf_idx - 1] = 0;
break :buf self.buf[self.buf_start .. self.buf_idx - 1 :0];
};
if (mem.eql(u8, self.temp_state.key, "id")) {
switch (self.command) {
@@ -1408,7 +1445,11 @@ pub const Parser = struct {
}
fn endSemanticOptionValue(self: *Parser) void {
const value = self.buf[self.buf_start..self.buf_idx];
const value = value: {
self.buf[self.buf_idx] = 0;
defer self.buf_idx += 1;
break :value self.buf[self.buf_start..self.buf_idx :0];
};
if (mem.eql(u8, self.temp_state.key, "aid")) {
switch (self.command) {
@@ -1465,7 +1506,9 @@ pub const Parser = struct {
}
fn endString(self: *Parser) void {
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx];
self.buf[self.buf_idx] = 0;
defer self.buf_idx += 1;
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx :0];
}
fn endConEmuSleepValue(self: *Parser) void {
@@ -1559,15 +1602,25 @@ pub const Parser = struct {
}
fn endAllocableString(self: *Parser) void {
const alloc = self.alloc.?;
const list = self.buf_dynamic.?;
self.temp_state.str.* = list.items;
list.append(alloc, 0) catch {
log.warn("allocation failed on allocable string termination", .{});
self.temp_state.str.* = "";
return;
};
self.temp_state.str.* = list.items[0 .. list.items.len - 1 :0];
}
/// End the sequence and return the command, if any. If the return value
/// is null, then no valid command was found. The optional terminator_ch
/// is the final character in the OSC sequence. This is used to determine
/// the response terminator.
pub fn end(self: *Parser, terminator_ch: ?u8) ?Command {
///
/// The returned pointer is only valid until the next call to the parser.
/// Callers should copy out any data they wish to retain across calls.
pub fn end(self: *Parser, terminator_ch: ?u8) ?*Command {
if (!self.complete) {
if (comptime !builtin.is_test) log.warn(
"invalid OSC command: {s}",
@@ -1626,7 +1679,7 @@ pub const Parser = struct {
else => {},
}
return self.command;
return &self.command;
}
};
@@ -1642,7 +1695,7 @@ test "OSC: change_window_title" {
p.next(';');
p.next('a');
p.next('b');
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_title);
try testing.expectEqualStrings("ab", cmd.change_window_title);
}
@@ -1655,7 +1708,7 @@ test "OSC: change_window_title with 2" {
p.next(';');
p.next('a');
p.next('b');
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_title);
try testing.expectEqualStrings("ab", cmd.change_window_title);
}
@@ -1677,7 +1730,7 @@ test "OSC: change_window_title with utf8" {
p.next(0xE2);
p.next(0x80);
p.next(0x90);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_title);
try testing.expectEqualStrings("", cmd.change_window_title);
}
@@ -1688,7 +1741,7 @@ test "OSC: change_window_title empty" {
var p: Parser = .init();
p.next('2');
p.next(';');
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_title);
try testing.expectEqualStrings("", cmd.change_window_title);
}
@@ -1701,7 +1754,7 @@ test "OSC: change_window_icon" {
p.next(';');
p.next('a');
p.next('b');
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_icon);
try testing.expectEqualStrings("ab", cmd.change_window_icon);
}
@@ -1714,7 +1767,7 @@ test "OSC: prompt_start" {
const input = "133;A";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expect(cmd.prompt_start.aid == null);
try testing.expect(cmd.prompt_start.redraw);
@@ -1728,7 +1781,7 @@ test "OSC: prompt_start with single option" {
const input = "133;A;aid=14";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expectEqualStrings("14", cmd.prompt_start.aid.?);
}
@@ -1741,7 +1794,7 @@ test "OSC: prompt_start with redraw disabled" {
const input = "133;A;redraw=0";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expect(!cmd.prompt_start.redraw);
}
@@ -1754,7 +1807,7 @@ test "OSC: prompt_start with redraw invalid value" {
const input = "133;A;redraw=42";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expect(cmd.prompt_start.redraw);
try testing.expect(cmd.prompt_start.kind == .primary);
@@ -1768,7 +1821,7 @@ test "OSC: prompt_start with continuation" {
const input = "133;A;k=c";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expect(cmd.prompt_start.kind == .continuation);
}
@@ -1781,7 +1834,7 @@ test "OSC: prompt_start with secondary" {
const input = "133;A;k=s";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_start);
try testing.expect(cmd.prompt_start.kind == .secondary);
}
@@ -1794,7 +1847,7 @@ test "OSC: end_of_command no exit code" {
const input = "133;D";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .end_of_command);
}
@@ -1806,7 +1859,7 @@ test "OSC: end_of_command with exit code" {
const input = "133;D;25";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .end_of_command);
try testing.expectEqual(@as(u8, 25), cmd.end_of_command.exit_code.?);
}
@@ -1819,7 +1872,7 @@ test "OSC: prompt_end" {
const input = "133;B";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .prompt_end);
}
@@ -1831,7 +1884,7 @@ test "OSC: end_of_input" {
const input = "133;C";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .end_of_input);
}
@@ -1843,7 +1896,7 @@ test "OSC: get/set clipboard" {
const input = "52;s;?";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .clipboard_contents);
try testing.expect(cmd.clipboard_contents.kind == 's');
try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
@@ -1857,7 +1910,7 @@ test "OSC: get/set clipboard (optional parameter)" {
const input = "52;;?";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .clipboard_contents);
try testing.expect(cmd.clipboard_contents.kind == 'c');
try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
@@ -1872,7 +1925,7 @@ test "OSC: get/set clipboard with allocator" {
const input = "52;s;?";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .clipboard_contents);
try testing.expect(cmd.clipboard_contents.kind == 's');
try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
@@ -1887,7 +1940,7 @@ test "OSC: clear clipboard" {
const input = "52;;";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .clipboard_contents);
try testing.expect(cmd.clipboard_contents.kind == 'c');
try testing.expectEqualStrings("", cmd.clipboard_contents.data);
@@ -1901,7 +1954,7 @@ test "OSC: report pwd" {
const input = "7;file:///tmp/example";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .report_pwd);
try testing.expectEqualStrings("file:///tmp/example", cmd.report_pwd.value);
}
@@ -1913,7 +1966,7 @@ test "OSC: report pwd empty" {
const input = "7;";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .report_pwd);
try testing.expectEqualStrings("", cmd.report_pwd.value);
}
@@ -1926,7 +1979,7 @@ test "OSC: pointer cursor" {
const input = "22;pointer";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?;
const cmd = p.end(null).?.*;
try testing.expect(cmd == .mouse_shape);
try testing.expectEqualStrings("pointer", cmd.mouse_shape.value);
}
@@ -1943,6 +1996,36 @@ test "OSC: longer than buffer" {
try testing.expect(p.complete == false);
}
test "OSC: one shorter than buffer length" {
const testing = std.testing;
var p: Parser = .init();
const prefix = "0;";
const title = "a" ** (Parser.MAX_BUF - prefix.len - 1);
const input = prefix ++ title;
for (input) |ch| p.next(ch);
const cmd = p.end(null).?.*;
try testing.expect(cmd == .change_window_title);
try testing.expectEqualStrings(title, cmd.change_window_title);
}
test "OSC: exactly at buffer length" {
const testing = std.testing;
var p: Parser = .init();
const prefix = "0;";
const title = "a" ** (Parser.MAX_BUF - prefix.len);
const input = prefix ++ title;
for (input) |ch| p.next(ch);
// This should be null because we always reserve space for a null terminator.
try testing.expect(p.end(null) == null);
try testing.expect(p.complete == false);
}
test "OSC: OSC 9;1 ConEmu sleep" {
const testing = std.testing;
@@ -1951,7 +2034,7 @@ test "OSC: OSC 9;1 ConEmu sleep" {
const input = "9;1;420";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_sleep);
try testing.expectEqual(420, cmd.conemu_sleep.duration_ms);
@@ -1965,7 +2048,7 @@ test "OSC: OSC 9;1 ConEmu sleep with no value default to 100ms" {
const input = "9;1;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_sleep);
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
@@ -1979,7 +2062,7 @@ test "OSC: OSC 9;1 conemu sleep cannot exceed 10000ms" {
const input = "9;1;12345";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_sleep);
try testing.expectEqual(10000, cmd.conemu_sleep.duration_ms);
@@ -1993,7 +2076,7 @@ test "OSC: OSC 9;1 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 == .conemu_sleep);
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
@@ -2007,7 +2090,7 @@ test "OSC: OSC 9;1 conemu sleep -> desktop notification 1" {
const input = "9;1";
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.expectEqualStrings("1", cmd.show_desktop_notification.body);
@@ -2021,7 +2104,7 @@ test "OSC: OSC 9;1 conemu sleep -> desktop notification 2" {
const input = "9;1a";
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.expectEqualStrings("1a", cmd.show_desktop_notification.body);
@@ -2035,7 +2118,7 @@ test "OSC: OSC 9 show desktop notification" {
const input = "9;Hello world";
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.expectEqualStrings("", cmd.show_desktop_notification.title);
try testing.expectEqualStrings("Hello world", cmd.show_desktop_notification.body);
@@ -2049,7 +2132,7 @@ test "OSC: OSC 9 show single character desktop notification" {
const input = "9;H";
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.expectEqualStrings("", cmd.show_desktop_notification.title);
try testing.expectEqualStrings("H", cmd.show_desktop_notification.body);
@@ -2063,7 +2146,7 @@ test "OSC: OSC 777 show desktop notification with title" {
const input = "777;notify;Title;Body";
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.expectEqualStrings(cmd.show_desktop_notification.title, "Title");
try testing.expectEqualStrings(cmd.show_desktop_notification.body, "Body");
@@ -2077,7 +2160,7 @@ test "OSC: OSC 9;2 ConEmu message box" {
const input = "9;2;hello world";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_show_message_box);
try testing.expectEqualStrings("hello world", cmd.conemu_show_message_box);
}
@@ -2090,7 +2173,7 @@ test "OSC: 9;2 ConEmu message box invalid input" {
const input = "9;2";
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.expectEqualStrings("2", cmd.show_desktop_notification.body);
}
@@ -2103,7 +2186,7 @@ test "OSC: 9;2 ConEmu message box empty message" {
const input = "9;2;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_show_message_box);
try testing.expectEqualStrings("", cmd.conemu_show_message_box);
}
@@ -2116,7 +2199,7 @@ test "OSC: 9;2 ConEmu message box spaces only message" {
const input = "9;2; ";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_show_message_box);
try testing.expectEqualStrings(" ", cmd.conemu_show_message_box);
}
@@ -2129,7 +2212,7 @@ test "OSC: OSC 9;2 message box -> desktop notification 1" {
const input = "9;2";
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.expectEqualStrings("2", cmd.show_desktop_notification.body);
@@ -2143,7 +2226,7 @@ test "OSC: OSC 9;2 message box -> desktop notification 2" {
const input = "9;2a";
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.expectEqualStrings("2a", cmd.show_desktop_notification.body);
@@ -2157,7 +2240,7 @@ test "OSC: 9;3 ConEmu change tab title" {
const input = "9;3;foo bar";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_change_tab_title);
try testing.expectEqualStrings("foo bar", cmd.conemu_change_tab_title.value);
}
@@ -2170,7 +2253,7 @@ test "OSC: 9;3 ConEmu change tab title reset" {
const input = "9;3;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
const expected_command: Command = .{ .conemu_change_tab_title = .reset };
try testing.expectEqual(expected_command, cmd);
@@ -2184,7 +2267,7 @@ test "OSC: 9;3 ConEmu change tab title spaces only" {
const input = "9;3; ";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_change_tab_title);
try testing.expectEqualStrings(" ", cmd.conemu_change_tab_title.value);
@@ -2198,7 +2281,7 @@ test "OSC: OSC 9;3 change tab title -> desktop notification 1" {
const input = "9;3";
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.expectEqualStrings("3", cmd.show_desktop_notification.body);
@@ -2212,7 +2295,7 @@ test "OSC: OSC 9;3 message box -> desktop notification 2" {
const input = "9;3a";
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.expectEqualStrings("3a", cmd.show_desktop_notification.body);
@@ -2226,7 +2309,7 @@ test "OSC: OSC 9;4 ConEmu progress set" {
const input = "9;4;1;100";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .set);
try testing.expect(cmd.conemu_progress_report.progress == 100);
@@ -2240,7 +2323,7 @@ test "OSC: OSC 9;4 ConEmu progress set overflow" {
const input = "9;4;1;900";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .set);
try testing.expectEqual(100, cmd.conemu_progress_report.progress);
@@ -2254,7 +2337,7 @@ test "OSC: OSC 9;4 ConEmu progress set single digit" {
const input = "9;4;1;9";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .set);
try testing.expect(cmd.conemu_progress_report.progress == 9);
@@ -2268,7 +2351,7 @@ test "OSC: OSC 9;4 ConEmu progress set double digit" {
const input = "9;4;1;94";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .set);
try testing.expectEqual(94, cmd.conemu_progress_report.progress);
@@ -2282,7 +2365,7 @@ test "OSC: OSC 9;4 ConEmu progress set extra semicolon ignored" {
const input = "9;4;1;100";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .set);
try testing.expectEqual(100, cmd.conemu_progress_report.progress);
@@ -2296,7 +2379,7 @@ test "OSC: OSC 9;4 ConEmu progress remove with no progress" {
const input = "9;4;0;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .remove);
try testing.expect(cmd.conemu_progress_report.progress == null);
@@ -2310,7 +2393,7 @@ test "OSC: OSC 9;4 ConEmu progress remove with double semicolon" {
const input = "9;4;0;;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .remove);
try testing.expect(cmd.conemu_progress_report.progress == null);
@@ -2324,7 +2407,7 @@ test "OSC: OSC 9;4 ConEmu progress remove ignores progress" {
const input = "9;4;0;100";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .remove);
try testing.expect(cmd.conemu_progress_report.progress == null);
@@ -2338,7 +2421,7 @@ test "OSC: OSC 9;4 ConEmu progress remove extra semicolon" {
const input = "9;4;0;100;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .remove);
}
@@ -2351,7 +2434,7 @@ test "OSC: OSC 9;4 ConEmu progress error" {
const input = "9;4;2";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .@"error");
try testing.expect(cmd.conemu_progress_report.progress == null);
@@ -2365,7 +2448,7 @@ test "OSC: OSC 9;4 ConEmu progress error with progress" {
const input = "9;4;2;100";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .@"error");
try testing.expect(cmd.conemu_progress_report.progress == 100);
@@ -2379,7 +2462,7 @@ test "OSC: OSC 9;4 progress pause" {
const input = "9;4;4";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .pause);
try testing.expect(cmd.conemu_progress_report.progress == null);
@@ -2393,7 +2476,7 @@ test "OSC: OSC 9;4 ConEmu progress pause with progress" {
const input = "9;4;4;100";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_progress_report);
try testing.expect(cmd.conemu_progress_report.state == .pause);
try testing.expect(cmd.conemu_progress_report.progress == 100);
@@ -2407,7 +2490,7 @@ test "OSC: OSC 9;4 progress -> desktop notification 1" {
const input = "9;4";
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.expectEqualStrings("4", cmd.show_desktop_notification.body);
@@ -2421,7 +2504,7 @@ test "OSC: OSC 9;4 progress -> desktop notification 2" {
const input = "9;4;";
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.expectEqualStrings("4;", cmd.show_desktop_notification.body);
@@ -2435,7 +2518,7 @@ test "OSC: OSC 9;4 progress -> desktop notification 3" {
const input = "9;4;5";
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.expectEqualStrings("4;5", cmd.show_desktop_notification.body);
@@ -2449,7 +2532,7 @@ test "OSC: OSC 9;4 progress -> desktop notification 4" {
const input = "9;4;5a";
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.expectEqualStrings("4;5a", cmd.show_desktop_notification.body);
@@ -2463,7 +2546,7 @@ test "OSC: OSC 9;5 ConEmu wait input" {
const input = "9;5";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_wait_input);
}
@@ -2475,7 +2558,7 @@ test "OSC: OSC 9;5 ConEmu wait ignores trailing characters" {
const input = "9;5;foo";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_wait_input);
}
@@ -2499,7 +2582,7 @@ test "OSC: hyperlink" {
const input = "8;;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}
@@ -2512,7 +2595,7 @@ test "OSC: hyperlink with id set" {
const input = "8;id=foo;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
@@ -2526,7 +2609,7 @@ test "OSC: hyperlink with empty id" {
const input = "8;id=;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqual(null, cmd.hyperlink_start.id);
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
@@ -2540,7 +2623,7 @@ test "OSC: hyperlink with incomplete key" {
const input = "8;id;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqual(null, cmd.hyperlink_start.id);
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
@@ -2554,7 +2637,7 @@ test "OSC: hyperlink with empty key" {
const input = "8;=value;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqual(null, cmd.hyperlink_start.id);
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
@@ -2568,7 +2651,7 @@ test "OSC: hyperlink with empty key and id" {
const input = "8;=value:id=foo;http://example.com";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_start);
try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
@@ -2594,7 +2677,7 @@ test "OSC: hyperlink end" {
const input = "8;;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .hyperlink_end);
}
@@ -2608,7 +2691,7 @@ test "OSC: kitty color protocol" {
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .kitty_color_protocol);
try testing.expectEqual(@as(usize, 9), cmd.kitty_color_protocol.list.items.len);
{
@@ -2690,7 +2773,7 @@ test "OSC: kitty color protocol double reset" {
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .kitty_color_protocol);
p.reset();
@@ -2706,7 +2789,7 @@ test "OSC: kitty color protocol reset after invalid" {
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .kitty_color_protocol);
p.reset();
@@ -2727,7 +2810,7 @@ test "OSC: kitty color protocol no key" {
const input = "21;";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .kitty_color_protocol);
try testing.expectEqual(0, cmd.kitty_color_protocol.list.items.len);
}
@@ -2741,7 +2824,7 @@ test "OSC: 9;6: ConEmu guimacro 1" {
const input = "9;6;a";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_guimacro);
try testing.expectEqualStrings("a", cmd.conemu_guimacro);
}
@@ -2755,7 +2838,7 @@ test "OSC: 9;6: ConEmu guimacro 2" {
const input = "9;6;ab";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
const cmd = p.end('\x1b').?.*;
try testing.expect(cmd == .conemu_guimacro);
try testing.expectEqualStrings("ab", cmd.conemu_guimacro);
}
@@ -2769,7 +2852,7 @@ test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
const input = "9;6";
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.expectEqualStrings("6", cmd.show_desktop_notification.body);
}