mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
osc: refactor parsing helper functions into separate files (#10233)
Following up on #9950, refactor the parsing helper functions into separate files.
This commit is contained in:
2337
src/terminal/osc.zig
2337
src/terminal/osc.zig
File diff suppressed because it is too large
Load Diff
27
src/terminal/osc/parsers.zig
Normal file
27
src/terminal/osc/parsers.zig
Normal file
@@ -0,0 +1,27 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const change_window_icon = @import("parsers/change_window_icon.zig");
|
||||
pub const change_window_title = @import("parsers/change_window_title.zig");
|
||||
pub const clipboard_operation = @import("parsers/clipboard_operation.zig");
|
||||
pub const color = @import("parsers/color.zig");
|
||||
pub const hyperlink = @import("parsers/hyperlink.zig");
|
||||
pub const kitty_color = @import("parsers/kitty_color.zig");
|
||||
pub const mouse_shape = @import("parsers/mouse_shape.zig");
|
||||
pub const osc9 = @import("parsers/osc9.zig");
|
||||
pub const report_pwd = @import("parsers/report_pwd.zig");
|
||||
pub const rxvt_extension = @import("parsers/rxvt_extension.zig");
|
||||
pub const semantic_prompt = @import("parsers/semantic_prompt.zig");
|
||||
|
||||
test {
|
||||
_ = change_window_icon;
|
||||
_ = change_window_title;
|
||||
_ = clipboard_operation;
|
||||
_ = color;
|
||||
_ = hyperlink;
|
||||
_ = kitty_color;
|
||||
_ = mouse_shape;
|
||||
_ = osc9;
|
||||
_ = report_pwd;
|
||||
_ = rxvt_extension;
|
||||
_ = semantic_prompt;
|
||||
}
|
||||
33
src/terminal/osc/parsers/change_window_icon.zig
Normal file
33
src/terminal/osc/parsers/change_window_icon.zig
Normal file
@@ -0,0 +1,33 @@
|
||||
const std = @import("std");
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
/// Parse OSC 1
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
parser.command = .{
|
||||
.change_window_icon = data[0 .. data.len - 1 :0],
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 1: change_window_icon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
p.next('1');
|
||||
p.next(';');
|
||||
p.next('a');
|
||||
p.next('b');
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .change_window_icon);
|
||||
try testing.expectEqualStrings("ab", cmd.change_window_icon);
|
||||
}
|
||||
119
src/terminal/osc/parsers/change_window_title.zig
Normal file
119
src/terminal/osc/parsers/change_window_title.zig
Normal file
@@ -0,0 +1,119 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
/// Parse OSC 0 and OSC 2
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
parser.command = .{
|
||||
.change_window_title = data[0 .. data.len - 1 :0],
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 0: change_window_title" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
p.next('0');
|
||||
p.next(';');
|
||||
p.next('a');
|
||||
p.next('b');
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .change_window_title);
|
||||
try testing.expectEqualStrings("ab", cmd.change_window_title);
|
||||
}
|
||||
|
||||
test "OSC 0: longer than buffer" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "0;" ++ "a" ** (Parser.MAX_BUF + 2);
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
try testing.expect(p.end(null) == null);
|
||||
}
|
||||
|
||||
test "OSC 0: one shorter than buffer length" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const prefix = "0;";
|
||||
const title = "a" ** (Parser.MAX_BUF - 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 0: exactly at buffer length" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const prefix = "0;";
|
||||
const title = "a" ** Parser.MAX_BUF;
|
||||
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);
|
||||
}
|
||||
test "OSC 2: change_window_title with 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
p.next('2');
|
||||
p.next(';');
|
||||
p.next('a');
|
||||
p.next('b');
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .change_window_title);
|
||||
try testing.expectEqualStrings("ab", cmd.change_window_title);
|
||||
}
|
||||
|
||||
test "OSC 2: change_window_title with utf8" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
p.next('2');
|
||||
p.next(';');
|
||||
// '—' EM DASH U+2014 (E2 80 94)
|
||||
p.next(0xE2);
|
||||
p.next(0x80);
|
||||
p.next(0x94);
|
||||
|
||||
p.next(' ');
|
||||
// '‐' HYPHEN U+2010 (E2 80 90)
|
||||
// Intententionally chosen to conflict with the 0x90 C1 control
|
||||
p.next(0xE2);
|
||||
p.next(0x80);
|
||||
p.next(0x90);
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .change_window_title);
|
||||
try testing.expectEqualStrings("— ‐", cmd.change_window_title);
|
||||
}
|
||||
|
||||
test "OSC 2: change_window_title empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
p.next('2');
|
||||
p.next(';');
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .change_window_title);
|
||||
try testing.expectEqualStrings("", cmd.change_window_title);
|
||||
}
|
||||
106
src/terminal/osc/parsers/clipboard_operation.zig
Normal file
106
src/terminal/osc/parsers/clipboard_operation.zig
Normal file
@@ -0,0 +1,106 @@
|
||||
const std = @import("std");
|
||||
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
/// Parse OSC 52
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
assert(parser.state == .@"52");
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
if (data.len == 1) {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
if (data[0] == ';') {
|
||||
parser.command = .{
|
||||
.clipboard_contents = .{
|
||||
.kind = 'c',
|
||||
.data = data[1 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
if (data.len < 2) {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
if (data[1] != ';') {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
parser.command = .{
|
||||
.clipboard_contents = .{
|
||||
.kind = data[0],
|
||||
.data = data[2 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
}
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 52: get/set clipboard" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "52;s;?";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 52: get/set clipboard (optional parameter)" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "52;;?";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 52: get/set clipboard with allocator" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "52;s;?";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 52: clear clipboard" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "52;;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const DynamicColor = @import("../color.zig").Dynamic;
|
||||
const SpecialColor = @import("../color.zig").Special;
|
||||
const RGB = @import("../color.zig").RGB;
|
||||
|
||||
pub const ParseError = Allocator.Error || error{
|
||||
const DynamicColor = @import("../../color.zig").Dynamic;
|
||||
const SpecialColor = @import("../../color.zig").Special;
|
||||
const RGB = @import("../../color.zig").RGB;
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
const log = std.log.scoped(.osc_color);
|
||||
|
||||
const ParseError = Allocator.Error || error{
|
||||
MissingOperation,
|
||||
};
|
||||
|
||||
@@ -36,6 +41,76 @@ pub const Operation = enum {
|
||||
osc_119,
|
||||
};
|
||||
|
||||
/// Parse OSCs 4, 5, 10-19, 104, 110-119
|
||||
pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command {
|
||||
const alloc = parser.alloc orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
// If we've collected any extra data parse that, otherwise use an empty
|
||||
// string.
|
||||
const data = data: {
|
||||
const writer = parser.writer orelse break :data "";
|
||||
break :data writer.buffered();
|
||||
};
|
||||
// Check and make sure that we're parsing the correct OSCs
|
||||
const op: Operation = switch (parser.state) {
|
||||
.@"4" => .osc_4,
|
||||
.@"5" => .osc_5,
|
||||
.@"10" => .osc_10,
|
||||
.@"11" => .osc_11,
|
||||
.@"12" => .osc_12,
|
||||
.@"13" => .osc_13,
|
||||
.@"14" => .osc_14,
|
||||
.@"15" => .osc_15,
|
||||
.@"16" => .osc_16,
|
||||
.@"17" => .osc_17,
|
||||
.@"18" => .osc_18,
|
||||
.@"19" => .osc_19,
|
||||
.@"104" => .osc_104,
|
||||
.@"110" => .osc_110,
|
||||
.@"111" => .osc_111,
|
||||
.@"112" => .osc_112,
|
||||
.@"113" => .osc_113,
|
||||
.@"114" => .osc_114,
|
||||
.@"115" => .osc_115,
|
||||
.@"116" => .osc_116,
|
||||
.@"117" => .osc_117,
|
||||
.@"118" => .osc_118,
|
||||
.@"119" => .osc_119,
|
||||
else => {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
};
|
||||
parser.command = .{
|
||||
.color_operation = .{
|
||||
.op = op,
|
||||
.requests = parseColor(alloc, op, data) catch |err| list: {
|
||||
log.info(
|
||||
"failed to parse OSC {t} color request err={} data={s}",
|
||||
.{ parser.state, err, data },
|
||||
);
|
||||
break :list .{};
|
||||
},
|
||||
.terminator = .init(terminator_ch),
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 4: empty param" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "4;;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b');
|
||||
try testing.expect(cmd == null);
|
||||
}
|
||||
|
||||
/// Parse any color operation string. This should NOT include the operation
|
||||
/// itself, but only the body of the operation. e.g. for "4;a;b;c" the body
|
||||
/// should be "a;b;c" and the operation should be set accordingly.
|
||||
@@ -46,7 +121,7 @@ pub const Operation = enum {
|
||||
/// request) but grants us an easier to understand and testable implementation.
|
||||
///
|
||||
/// If color changing ends up being a bottleneck we can optimize this later.
|
||||
pub fn parse(
|
||||
fn parseColor(
|
||||
alloc: Allocator,
|
||||
op: Operation,
|
||||
buf: []const u8,
|
||||
@@ -295,7 +370,7 @@ test "OSC 4:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
var list = try parseColor(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -317,7 +392,7 @@ test "OSC 4:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
var list = try parseColor(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -336,7 +411,7 @@ test "OSC 4:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
var list = try parseColor(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -360,7 +435,7 @@ test "OSC 4:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
var list = try parseColor(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -387,7 +462,7 @@ test "OSC 4:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
var list = try parseColor(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -419,7 +494,7 @@ test "OSC 5:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_5, body);
|
||||
var list = try parseColor(alloc, .osc_5, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -439,7 +514,7 @@ test "OSC 4: multiple requests" {
|
||||
|
||||
// printf '\e]4;0;red;1;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
var list = try parseColor(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;1;blue",
|
||||
@@ -465,7 +540,7 @@ test "OSC 4: multiple requests" {
|
||||
// Multiple requests with same index overwrite each other
|
||||
// printf '\e]4;0;red;0;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
var list = try parseColor(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;0;blue",
|
||||
@@ -505,7 +580,7 @@ test "OSC 104:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_104, body);
|
||||
var list = try parseColor(alloc, .osc_104, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -529,7 +604,7 @@ test "OSC 104:" {
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_104, body);
|
||||
var list = try parseColor(alloc, .osc_104, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -544,7 +619,7 @@ test "OSC 104: empty index" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "0;;1");
|
||||
var list = try parseColor(alloc, .osc_104, "0;;1");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -561,7 +636,7 @@ test "OSC 104: invalid index" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "ffff;1");
|
||||
var list = try parseColor(alloc, .osc_104, "ffff;1");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -574,7 +649,7 @@ test "OSC 104: reset all" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "");
|
||||
var list = try parseColor(alloc, .osc_104, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -587,7 +662,7 @@ test "OSC 105: reset all" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_105, "");
|
||||
var list = try parseColor(alloc, .osc_105, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -611,7 +686,7 @@ test "OSC 10: OSC 11: OSC 12: OSC: 13: OSC 14: OSC 15: OSC: 16: OSC 17: OSC 18:
|
||||
// Example script:
|
||||
// printf '\e]10;red\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, "red");
|
||||
var list = try parseColor(alloc, op, "red");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -632,7 +707,7 @@ test "OSC 10: OSC 11: OSC 12: OSC: 13: OSC 14: OSC 15: OSC: 16: OSC 17: OSC 18:
|
||||
// Example script:
|
||||
// printf '\e]11;red;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
var list = try parseColor(
|
||||
alloc,
|
||||
.osc_11,
|
||||
"red;blue",
|
||||
@@ -671,7 +746,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
|
||||
// Example script:
|
||||
// printf '\e]110\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, "");
|
||||
var list = try parseColor(alloc, op, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -684,7 +759,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
|
||||
//
|
||||
// printf '\e]110;\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, ";");
|
||||
var list = try parseColor(alloc, op, ";");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
@@ -697,7 +772,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
|
||||
//
|
||||
// printf '\e]110 \e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, " ");
|
||||
var list = try parseColor(alloc, op, " ");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(0, list.count());
|
||||
}
|
||||
164
src/terminal/osc/parsers/hyperlink.zig
Normal file
164
src/terminal/osc/parsers/hyperlink.zig
Normal file
@@ -0,0 +1,164 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
const log = std.log.scoped(.osc_hyperlink);
|
||||
|
||||
/// Parse OSC 8 hyperlinks
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
const s = std.mem.indexOfScalar(u8, data, ';') orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
|
||||
parser.command = .{
|
||||
.hyperlink_start = .{
|
||||
.uri = data[s + 1 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
|
||||
data[s] = 0;
|
||||
const kvs = data[0 .. s + 1];
|
||||
std.mem.replaceScalar(u8, kvs, ':', 0);
|
||||
var kv_start: usize = 0;
|
||||
while (kv_start < kvs.len) {
|
||||
const kv_end = std.mem.indexOfScalarPos(u8, kvs, kv_start + 1, 0) orelse break;
|
||||
const kv = data[kv_start .. kv_end + 1];
|
||||
const v = std.mem.indexOfScalar(u8, kv, '=') orelse break;
|
||||
const key = kv[0..v];
|
||||
const value = kv[v + 1 .. kv.len - 1 :0];
|
||||
if (std.mem.eql(u8, key, "id")) {
|
||||
if (value.len > 0) parser.command.hyperlink_start.id = value;
|
||||
} else {
|
||||
log.warn("unknown hyperlink option: '{s}'", .{key});
|
||||
}
|
||||
kv_start = kv_end + 1;
|
||||
}
|
||||
|
||||
if (parser.command.hyperlink_start.uri.len == 0) {
|
||||
if (parser.command.hyperlink_start.id != null) {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
parser.command = .hyperlink_end;
|
||||
}
|
||||
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .hyperlink_start);
|
||||
try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with id set" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;id=foo;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with empty id" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;id=;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with incomplete key" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;id;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with empty key" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;=value;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with empty key and id" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;=value:id=foo;http://example.com";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink with empty uri" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;id=foo;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b');
|
||||
try testing.expect(cmd == null);
|
||||
}
|
||||
|
||||
test "OSC 8: hyperlink end" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "8;;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .hyperlink_end);
|
||||
}
|
||||
212
src/terminal/osc/parsers/kitty_color.zig
Normal file
212
src/terminal/osc/parsers/kitty_color.zig
Normal file
@@ -0,0 +1,212 @@
|
||||
const std = @import("std");
|
||||
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
const kitty_color = @import("../../kitty/color.zig");
|
||||
const RGB = @import("../../color.zig").RGB;
|
||||
|
||||
const log = std.log.scoped(.osc_kitty_color);
|
||||
|
||||
/// Parse OSC 21, the Kitty Color Protocol.
|
||||
pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command {
|
||||
assert(parser.state == .@"21");
|
||||
|
||||
const alloc = parser.alloc orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
parser.command = .{
|
||||
.kitty_color_protocol = .{
|
||||
.list = .empty,
|
||||
.terminator = .init(terminator_ch),
|
||||
},
|
||||
};
|
||||
const list = &parser.command.kitty_color_protocol.list;
|
||||
const data = writer.buffered();
|
||||
var kv_it = std.mem.splitScalar(u8, data, ';');
|
||||
while (kv_it.next()) |kv| {
|
||||
if (list.items.len >= @as(usize, kitty_color.Kind.max) * 2) {
|
||||
log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{});
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
var it = std.mem.splitScalar(u8, kv, '=');
|
||||
const k = it.next() orelse continue;
|
||||
if (k.len == 0) {
|
||||
log.warn("zero length key in kitty color protocol", .{});
|
||||
continue;
|
||||
}
|
||||
const key = kitty_color.Kind.parse(k) orelse {
|
||||
log.warn("unknown key in kitty color protocol: {s}", .{k});
|
||||
continue;
|
||||
};
|
||||
const value = std.mem.trim(u8, it.rest(), " ");
|
||||
if (value.len == 0) {
|
||||
list.append(alloc, .{ .reset = key }) catch |err| {
|
||||
log.warn("unable to append kitty color protocol option: {}", .{err});
|
||||
continue;
|
||||
};
|
||||
} else if (std.mem.eql(u8, "?", value)) {
|
||||
list.append(alloc, .{ .query = key }) catch |err| {
|
||||
log.warn("unable to append kitty color protocol option: {}", .{err});
|
||||
continue;
|
||||
};
|
||||
} else {
|
||||
list.append(alloc, .{
|
||||
.set = .{
|
||||
.key = key,
|
||||
.color = RGB.parse(value) catch |err| switch (err) {
|
||||
error.InvalidFormat => {
|
||||
log.warn("invalid color format in kitty color protocol: {s}", .{value});
|
||||
continue;
|
||||
},
|
||||
},
|
||||
},
|
||||
}) catch |err| {
|
||||
log.warn("unable to append kitty color protocol option: {}", .{err});
|
||||
continue;
|
||||
};
|
||||
}
|
||||
}
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 21: kitty color protocol" {
|
||||
const testing = std.testing;
|
||||
const Kind = kitty_color.Kind;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
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').?.*;
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
try testing.expectEqual(@as(usize, 9), cmd.kitty_color_protocol.list.items.len);
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[0];
|
||||
try testing.expect(item == .query);
|
||||
try testing.expectEqual(Kind{ .special = .foreground }, item.query);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[1];
|
||||
try testing.expect(item == .set);
|
||||
try testing.expectEqual(Kind{ .special = .background }, item.set.key);
|
||||
try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[2];
|
||||
try testing.expect(item == .set);
|
||||
try testing.expectEqual(Kind{ .special = .cursor }, item.set.key);
|
||||
try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[3];
|
||||
try testing.expect(item == .reset);
|
||||
try testing.expectEqual(Kind{ .special = .cursor_text }, item.reset);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[4];
|
||||
try testing.expect(item == .reset);
|
||||
try testing.expectEqual(Kind{ .special = .visual_bell }, item.reset);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[5];
|
||||
try testing.expect(item == .query);
|
||||
try testing.expectEqual(Kind{ .special = .selection_background }, item.query);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[6];
|
||||
try testing.expect(item == .set);
|
||||
try testing.expectEqual(Kind{ .special = .selection_background }, item.set.key);
|
||||
try testing.expectEqual(@as(u8, 0xaa), item.set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xbb), item.set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xcc), item.set.color.b);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[7];
|
||||
try testing.expect(item == .query);
|
||||
try testing.expectEqual(Kind{ .palette = 2 }, item.query);
|
||||
}
|
||||
{
|
||||
const item = cmd.kitty_color_protocol.list.items[8];
|
||||
try testing.expect(item == .set);
|
||||
try testing.expectEqual(Kind{ .palette = 3 }, item.set.key);
|
||||
try testing.expectEqual(@as(u8, 0xff), item.set.color.r);
|
||||
try testing.expectEqual(@as(u8, 0xff), item.set.color.g);
|
||||
try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
|
||||
}
|
||||
}
|
||||
|
||||
test "OSC 21: kitty color protocol without allocator" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "21;foreground=?";
|
||||
for (input) |ch| p.next(ch);
|
||||
try testing.expect(p.end('\x1b') == null);
|
||||
}
|
||||
|
||||
test "OSC 21: kitty color protocol double reset" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
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').?.*;
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
|
||||
p.reset();
|
||||
p.reset();
|
||||
}
|
||||
|
||||
test "OSC 21: kitty color protocol reset after invalid" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
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').?.*;
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
|
||||
p.reset();
|
||||
|
||||
try testing.expectEqual(Parser.State.start, p.state);
|
||||
p.next('X');
|
||||
try testing.expectEqual(Parser.State.invalid, p.state);
|
||||
|
||||
p.reset();
|
||||
}
|
||||
|
||||
test "OSC 21: kitty color protocol no key" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "21;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
try testing.expectEqual(0, cmd.kitty_color_protocol.list.items.len);
|
||||
}
|
||||
39
src/terminal/osc/parsers/mouse_shape.zig
Normal file
39
src/terminal/osc/parsers/mouse_shape.zig
Normal file
@@ -0,0 +1,39 @@
|
||||
const std = @import("std");
|
||||
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
// Parse OSC 22
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
assert(parser.state == .@"22");
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
parser.command = .{
|
||||
.mouse_shape = .{
|
||||
.value = data[0 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 22: pointer cursor" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "22;pointer";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .mouse_shape);
|
||||
try testing.expectEqualStrings("pointer", cmd.mouse_shape.value);
|
||||
}
|
||||
766
src/terminal/osc/parsers/osc9.zig
Normal file
766
src/terminal/osc/parsers/osc9.zig
Normal file
@@ -0,0 +1,766 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
/// Parse OSC 9, which could be an iTerm2 notification or a ConEmu extension.
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Check first to see if this is a ConEmu OSC
|
||||
// https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
|
||||
conemu: {
|
||||
var data = writer.buffered();
|
||||
if (data.len == 0) break :conemu;
|
||||
switch (data[0]) {
|
||||
// Check for OSC 9;1 9;10 9;12
|
||||
'1' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
switch (data[1]) {
|
||||
// OSC 9;1
|
||||
';' => {
|
||||
parser.command = .{
|
||||
.conemu_sleep = .{
|
||||
.duration_ms = if (std.fmt.parseUnsigned(u16, data[2..], 10)) |num| @min(num, 10_000) else |_| 100,
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;10
|
||||
'0' => {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
// OSC 9;12
|
||||
'2' => {
|
||||
parser.command = .{
|
||||
.prompt_start = .{},
|
||||
};
|
||||
return &parser.command;
|
||||
},
|
||||
else => break :conemu,
|
||||
}
|
||||
},
|
||||
// OSC 9;2
|
||||
'2' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
data = writer.buffered();
|
||||
parser.command = .{
|
||||
.conemu_show_message_box = data[2 .. data.len - 1 :0],
|
||||
};
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;3
|
||||
'3' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
if (data.len == 2) {
|
||||
parser.command = .{
|
||||
.conemu_change_tab_title = .reset,
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
data = writer.buffered();
|
||||
parser.command = .{
|
||||
.conemu_change_tab_title = .{
|
||||
.value = data[2 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;4
|
||||
'4' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
if (data.len < 3) break :conemu;
|
||||
switch (data[2]) {
|
||||
'0' => {
|
||||
parser.command = .{
|
||||
.conemu_progress_report = .{
|
||||
.state = .remove,
|
||||
},
|
||||
};
|
||||
},
|
||||
'1' => {
|
||||
parser.command = .{
|
||||
.conemu_progress_report = .{
|
||||
.state = .set,
|
||||
.progress = 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
'2' => {
|
||||
parser.command = .{
|
||||
.conemu_progress_report = .{
|
||||
.state = .@"error",
|
||||
},
|
||||
};
|
||||
},
|
||||
'3' => {
|
||||
parser.command = .{
|
||||
.conemu_progress_report = .{
|
||||
.state = .indeterminate,
|
||||
},
|
||||
};
|
||||
},
|
||||
'4' => {
|
||||
parser.command = .{
|
||||
.conemu_progress_report = .{
|
||||
.state = .pause,
|
||||
},
|
||||
};
|
||||
},
|
||||
else => break :conemu,
|
||||
}
|
||||
switch (parser.command.conemu_progress_report.state) {
|
||||
.remove, .indeterminate => {},
|
||||
.set, .@"error", .pause => progress: {
|
||||
if (data.len < 4) break :progress;
|
||||
if (data[3] != ';') break :progress;
|
||||
// parse the progress value
|
||||
parser.command.conemu_progress_report.progress = value: {
|
||||
break :value @intCast(std.math.clamp(
|
||||
std.fmt.parseUnsigned(usize, data[4..], 10) catch break :value null,
|
||||
0,
|
||||
100,
|
||||
));
|
||||
};
|
||||
},
|
||||
}
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;5
|
||||
'5' => {
|
||||
parser.command = .conemu_wait_input;
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;6
|
||||
'6' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
data = writer.buffered();
|
||||
parser.command = .{
|
||||
.conemu_guimacro = data[2 .. data.len - 1 :0],
|
||||
};
|
||||
return &parser.command;
|
||||
},
|
||||
// OSC 9;7
|
||||
'7' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
// OSC 9;8
|
||||
'8' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
// OSC 9;9
|
||||
'9' => {
|
||||
if (data.len < 2) break :conemu;
|
||||
if (data[1] != ';') break :conemu;
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
else => break :conemu,
|
||||
}
|
||||
}
|
||||
|
||||
// If it's not a ConEmu OSC, it's an iTerm2 notification
|
||||
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
parser.command = .{
|
||||
.show_desktop_notification = .{
|
||||
.title = "",
|
||||
.body = data[0 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 9: show desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;Hello world";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9: show single character desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;H";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;1: ConEmu sleep" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1;420";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(420, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC 9;1: ConEmu sleep with no value default to 100ms" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC 9;1: conemu sleep cannot exceed 10000ms" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1;12345";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(10000, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC 9;1: conemu sleep invalid input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1;foo";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC 9;1: conemu sleep -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("1", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;1: conemu sleep -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;1a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("1a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;2: ConEmu message box" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2;hello world";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings("hello world", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC 9;2: ConEmu message box invalid input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;2: ConEmu message box empty message" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings("", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC 9;2: ConEmu message box spaces only message" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2; ";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings(" ", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC 9;2: message box -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;2: message box -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;2a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;3: ConEmu change tab title" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;3;foo bar";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_change_tab_title);
|
||||
try testing.expectEqualStrings("foo bar", cmd.conemu_change_tab_title.value);
|
||||
}
|
||||
|
||||
test "OSC 9;3: ConEmu change tab title reset" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;3;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
const expected_command: Command = .{ .conemu_change_tab_title = .reset };
|
||||
try testing.expectEqual(expected_command, cmd);
|
||||
}
|
||||
|
||||
test "OSC 9;3: ConEmu change tab title spaces only" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;3; ";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .conemu_change_tab_title);
|
||||
try testing.expectEqualStrings(" ", cmd.conemu_change_tab_title.value);
|
||||
}
|
||||
|
||||
test "OSC 9;3: change tab title -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;3";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("3", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;3: message box -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;3a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("3a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress set" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;1;100";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress set overflow" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;1;900";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress set single digit" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;1;9";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress set double digit" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;1;94";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress set extra semicolon ignored" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;1;100";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress remove with no progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;0;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress remove with double semicolon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;0;;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress remove ignores progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;0;100";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress remove extra semicolon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;0;100;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .remove);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress error" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress error with progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;2;100";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: progress pause" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;4";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: ConEmu progress pause with progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;4;100";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 9;4: progress -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;4: progress -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;4: progress -> desktop notification 3" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;5";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;5", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;4: progress -> desktop notification 4" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;4;5a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;5a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC 9;5: ConEmu wait input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;5";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_wait_input);
|
||||
}
|
||||
|
||||
test "OSC 9;5: ConEmu wait ignores trailing characters" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "9;5;foo";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_wait_input);
|
||||
}
|
||||
|
||||
test "OSC 9;6: ConEmu guimacro 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6;a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_guimacro);
|
||||
try testing.expectEqualStrings("a", cmd.conemu_guimacro);
|
||||
}
|
||||
|
||||
test "OSC: 9;6: ConEmu guimacro 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6;ab";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .conemu_guimacro);
|
||||
try testing.expectEqualStrings("ab", cmd.conemu_guimacro);
|
||||
}
|
||||
|
||||
test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?.*;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("6", cmd.show_desktop_notification.body);
|
||||
}
|
||||
48
src/terminal/osc/parsers/report_pwd.zig
Normal file
48
src/terminal/osc/parsers/report_pwd.zig
Normal file
@@ -0,0 +1,48 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
/// Parse OSC 7
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
parser.command = .{
|
||||
.report_pwd = .{
|
||||
.value = data[0 .. data.len - 1 :0],
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC 7: report pwd" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "7;file:///tmp/example";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .report_pwd);
|
||||
try testing.expectEqualStrings("file:///tmp/example", cmd.report_pwd.value);
|
||||
}
|
||||
|
||||
test "OSC 7: report pwd empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "7;";
|
||||
for (input) |ch| p.next(ch);
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .report_pwd);
|
||||
try testing.expectEqualStrings("", cmd.report_pwd.value);
|
||||
}
|
||||
59
src/terminal/osc/parsers/rxvt_extension.zig
Normal file
59
src/terminal/osc/parsers/rxvt_extension.zig
Normal file
@@ -0,0 +1,59 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
const log = std.log.scoped(.osc_rxvt_extension);
|
||||
|
||||
/// Parse OSC 777
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
// ensure that we are sentinel terminated
|
||||
writer.writeByte(0) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
const k = std.mem.indexOfScalar(u8, data, ';') orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const ext = data[0..k];
|
||||
if (!std.mem.eql(u8, ext, "notify")) {
|
||||
log.warn("unknown rxvt extension: {s}", .{ext});
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
const t = std.mem.indexOfScalarPos(u8, data, k + 1, ';') orelse {
|
||||
log.warn("rxvt notify extension is missing the title", .{});
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
data[t] = 0;
|
||||
const title = data[k + 1 .. t :0];
|
||||
const body = data[t + 1 .. data.len - 1 :0];
|
||||
parser.command = .{
|
||||
.show_desktop_notification = .{
|
||||
.title = title,
|
||||
.body = body,
|
||||
},
|
||||
};
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
test "OSC: OSC 777 show desktop notification with title" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "777;notify;Title;Body";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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");
|
||||
}
|
||||
694
src/terminal/osc/parsers/semantic_prompt.zig
Normal file
694
src/terminal/osc/parsers/semantic_prompt.zig
Normal file
@@ -0,0 +1,694 @@
|
||||
const std = @import("std");
|
||||
|
||||
const string_encoding = @import("../../../os/string_encoding.zig");
|
||||
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const Command = @import("../../osc.zig").Command;
|
||||
|
||||
const log = std.log.scoped(.osc_semantic_prompt);
|
||||
|
||||
/// Parse OSC 133, semantic prompts
|
||||
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
|
||||
const writer = parser.writer orelse {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
const data = writer.buffered();
|
||||
if (data.len == 0) {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
switch (data[0]) {
|
||||
'A' => prompt_start: {
|
||||
parser.command = .{
|
||||
.prompt_start = .{},
|
||||
};
|
||||
if (data.len == 1) break :prompt_start;
|
||||
if (data[1] != ';') {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
var it = SemanticPromptKVIterator.init(writer) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
while (it.next()) |kv| {
|
||||
if (std.mem.eql(u8, kv.key, "aid")) {
|
||||
parser.command.prompt_start.aid = kv.value;
|
||||
} else if (std.mem.eql(u8, kv.key, "redraw")) redraw: {
|
||||
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
|
||||
// Kitty supports a "redraw" option for prompt_start. I can't find
|
||||
// this documented anywhere but can see in the code that this is used
|
||||
// by shell environments to tell the terminal that the shell will NOT
|
||||
// redraw the prompt so we should attempt to resize it.
|
||||
parser.command.prompt_start.redraw = (value: {
|
||||
if (kv.value.len != 1) break :value null;
|
||||
switch (kv.value[0]) {
|
||||
'0' => break :value false,
|
||||
'1' => break :value true,
|
||||
else => break :value null,
|
||||
}
|
||||
}) orelse {
|
||||
log.info("OSC 133 A: invalid redraw value: {s}", .{kv.value});
|
||||
break :redraw;
|
||||
};
|
||||
} else if (std.mem.eql(u8, kv.key, "special_key")) redraw: {
|
||||
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
|
||||
parser.command.prompt_start.special_key = (value: {
|
||||
if (kv.value.len != 1) break :value null;
|
||||
switch (kv.value[0]) {
|
||||
'0' => break :value false,
|
||||
'1' => break :value true,
|
||||
else => break :value null,
|
||||
}
|
||||
}) orelse {
|
||||
log.info("OSC 133 A invalid special_key value: {s}", .{kv.value});
|
||||
break :redraw;
|
||||
};
|
||||
} else if (std.mem.eql(u8, kv.key, "click_events")) redraw: {
|
||||
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
|
||||
parser.command.prompt_start.click_events = (value: {
|
||||
if (kv.value.len != 1) break :value null;
|
||||
switch (kv.value[0]) {
|
||||
'0' => break :value false,
|
||||
'1' => break :value true,
|
||||
else => break :value null,
|
||||
}
|
||||
}) orelse {
|
||||
log.info("OSC 133 A invalid click_events value: {s}", .{kv.value});
|
||||
break :redraw;
|
||||
};
|
||||
} else if (std.mem.eql(u8, kv.key, "k")) k: {
|
||||
// The "k" marks the kind of prompt, or "primary" if we don't know.
|
||||
// This can be used to distinguish between the first (initial) prompt,
|
||||
// a continuation, etc.
|
||||
if (kv.value.len != 1) break :k;
|
||||
parser.command.prompt_start.kind = switch (kv.value[0]) {
|
||||
'c' => .continuation,
|
||||
's' => .secondary,
|
||||
'r' => .right,
|
||||
'i' => .primary,
|
||||
else => .primary,
|
||||
};
|
||||
} else log.info("OSC 133 A: unknown semantic prompt option: {s}", .{kv.key});
|
||||
}
|
||||
},
|
||||
'B' => prompt_end: {
|
||||
parser.command = .prompt_end;
|
||||
if (data.len == 1) break :prompt_end;
|
||||
if (data[1] != ';') {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
var it = SemanticPromptKVIterator.init(writer) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
while (it.next()) |kv| {
|
||||
log.info("OSC 133 B: unknown semantic prompt option: {s}", .{kv.key});
|
||||
}
|
||||
},
|
||||
'C' => end_of_input: {
|
||||
parser.command = .{
|
||||
.end_of_input = .{},
|
||||
};
|
||||
if (data.len == 1) break :end_of_input;
|
||||
if (data[1] != ';') {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
var it = SemanticPromptKVIterator.init(writer) catch {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
};
|
||||
while (it.next()) |kv| {
|
||||
if (std.mem.eql(u8, kv.key, "cmdline")) {
|
||||
parser.command.end_of_input.cmdline = string_encoding.printfQDecode(kv.value) catch null;
|
||||
} else if (std.mem.eql(u8, kv.key, "cmdline_url")) {
|
||||
parser.command.end_of_input.cmdline = string_encoding.urlPercentDecode(kv.value) catch null;
|
||||
} else {
|
||||
log.info("OSC 133 C: unknown semantic prompt option: {s}", .{kv.key});
|
||||
}
|
||||
}
|
||||
},
|
||||
'D' => {
|
||||
const exit_code: ?u8 = exit_code: {
|
||||
if (data.len == 1) break :exit_code null;
|
||||
if (data[1] != ';') {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
}
|
||||
break :exit_code std.fmt.parseUnsigned(u8, data[2..], 10) catch null;
|
||||
};
|
||||
parser.command = .{
|
||||
.end_of_command = .{
|
||||
.exit_code = exit_code,
|
||||
},
|
||||
};
|
||||
},
|
||||
else => {
|
||||
parser.state = .invalid;
|
||||
return null;
|
||||
},
|
||||
}
|
||||
return &parser.command;
|
||||
}
|
||||
|
||||
const SemanticPromptKVIterator = struct {
|
||||
index: usize,
|
||||
string: []u8,
|
||||
|
||||
pub const SemanticPromptKV = struct {
|
||||
key: [:0]u8,
|
||||
value: [:0]u8,
|
||||
};
|
||||
|
||||
pub fn init(writer: *std.Io.Writer) std.Io.Writer.Error!SemanticPromptKVIterator {
|
||||
// add a semicolon to make it easier to find and sentinel terminate the values
|
||||
try writer.writeByte(';');
|
||||
return .{
|
||||
.index = 0,
|
||||
.string = writer.buffered()[2..],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next(self: *SemanticPromptKVIterator) ?SemanticPromptKV {
|
||||
if (self.index >= self.string.len) return null;
|
||||
|
||||
const kv = kv: {
|
||||
const index = std.mem.indexOfScalarPos(u8, self.string, self.index, ';') orelse {
|
||||
self.index = self.string.len;
|
||||
return null;
|
||||
};
|
||||
self.string[index] = 0;
|
||||
const kv = self.string[self.index..index :0];
|
||||
self.index = index + 1;
|
||||
break :kv kv;
|
||||
};
|
||||
|
||||
const key = key: {
|
||||
const index = std.mem.indexOfScalar(u8, kv, '=') orelse break :key kv;
|
||||
kv[index] = 0;
|
||||
const key = kv[0..index :0];
|
||||
break :key key;
|
||||
};
|
||||
|
||||
const value = kv[key.len + 1 .. :0];
|
||||
|
||||
return .{
|
||||
.key = key,
|
||||
.value = value,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "OSC 133: prompt_start" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with single option" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;aid=14";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expectEqualStrings("14", cmd.prompt_start.aid.?);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with '=' in aid" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;aid=a=b;redraw=0";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expectEqualStrings("a=b", cmd.prompt_start.aid.?);
|
||||
try testing.expect(!cmd.prompt_start.redraw);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with redraw disabled" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;redraw=0";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(!cmd.prompt_start.redraw);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with redraw invalid value" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;redraw=42";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with continuation" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;k=c";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.kind == .continuation);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with secondary" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;k=s";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.kind == .secondary);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with special_key" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;special_key=1";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.special_key == true);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with special_key invalid" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;special_key=bobr";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.special_key == false);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with special_key 0" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;special_key=0";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.special_key == false);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with special_key empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;special_key=";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.special_key == false);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with click_events true" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;click_events=1";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.click_events == true);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with click_events false" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;click_events=0";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.click_events == false);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_start with click_events empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;A;click_events=";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_start);
|
||||
try testing.expect(cmd.prompt_start.click_events == false);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_command no exit code" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;D";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_command);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_command with exit code" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;D;25";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_command);
|
||||
try testing.expectEqual(@as(u8, 25), cmd.end_of_command.exit_code.?);
|
||||
}
|
||||
|
||||
test "OSC 133: prompt_end" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;B";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .prompt_end);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=echo bobr kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=echo bobr\\ kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 3" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=echo bobr\\nkurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr\nkurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 4" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=$'echo bobr kurwa'";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 5" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline='echo bobr kurwa'";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 6" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline='echo bobr kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 7" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=$'echo bobr kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 8" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=$'";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 9" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=$'";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline 10" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline=";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr%20kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 3" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr%3bkurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr;kurwa", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 4" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr%3kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 5" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr%kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 6" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr%kurwa";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 7" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr kurwa%20";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline != null);
|
||||
try testing.expectEqualStrings("echo bobr kurwa ", cmd.end_of_input.cmdline.?);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 8" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr kurwa%2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
|
||||
test "OSC 133: end_of_input with cmdline_url 9" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init(null);
|
||||
|
||||
const input = "133;C;cmdline_url=echo bobr kurwa%2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end(null).?.*;
|
||||
try testing.expect(cmd == .end_of_input);
|
||||
try testing.expect(cmd.end_of_input.cmdline == null);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ const stream = @import("stream.zig");
|
||||
const Action = stream.Action;
|
||||
const Screen = @import("Screen.zig");
|
||||
const modes = @import("modes.zig");
|
||||
const osc_color = @import("osc/color.zig");
|
||||
const osc_color = @import("osc/parsers/color.zig");
|
||||
const kitty_color = @import("kitty/color.zig");
|
||||
const Terminal = @import("Terminal.zig");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user