From 039f9231de874f49c4300a380f768d5dfda8f400 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 13 Nov 2024 19:46:23 -0800 Subject: [PATCH] terminal: parse CSI 22/23 for push/pop title Related to #2668 This just implements the VT stream parsing. The actual handling of these events still needs to be done. --- src/terminal/csi.zig | 8 ++ src/terminal/stream.zig | 165 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/src/terminal/csi.zig b/src/terminal/csi.zig index d2ee4da46..0cab9ed52 100644 --- a/src/terminal/csi.zig +++ b/src/terminal/csi.zig @@ -40,3 +40,11 @@ pub const SizeReportStyle = enum { csi_18_t, csi_21_t, }; + +/// XTWINOPS CSI 22/23 +pub const TitlePushPop = struct { + op: Op, + index: u16, + + pub const Op = enum { push, pop }; +}; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 703abbb38..b8d60a13f 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -1162,6 +1162,33 @@ pub fn Stream(comptime Handler: type) type { "ignoring CSI 21 t with extra parameters: {s}", .{input}, ), + inline 22, 23 => |number| if ((input.params.len == 2 or + input.params.len == 3) and + // we only support window title + (input.params[1] == 0 or + input.params[1] == 2)) + { + // push/pop title + if (@hasDecl(T, "pushPopTitle")) { + self.handler.pushPopTitle(.{ + .op = switch (number) { + 22 => .push, + 23 => .pop, + else => @compileError("unreachable"), + }, + .index = if (input.params.len == 3) + input.params[2] + else + 0, + }); + } else log.warn( + "ignoring unimplemented CSI 22/23 t", + .{}, + ); + } else log.warn( + "ignoring CSI 22/23 t with extra parameters: {s}", + .{input}, + ), else => log.warn( "ignoring CSI t with unimplemented parameter: {s}", .{input}, @@ -2162,3 +2189,141 @@ test "stream: invalid CSI t" { try s.nextSlice("\x1b[19t"); try testing.expectEqual(null, s.handler.style); } + +test "stream: CSI t push title" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[22;0t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .push, + .index = 0, + }, s.handler.op.?); +} + +test "stream: CSI t push title with explicit window" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[22;2t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .push, + .index = 0, + }, s.handler.op.?); +} + +test "stream: CSI t push title with explicit icon" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[22;1t"); + try testing.expectEqual(null, s.handler.op); +} + +test "stream: CSI t push title with index" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[22;0;5t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .push, + .index = 5, + }, s.handler.op.?); +} + +test "stream: CSI t pop title" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[23;0t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .pop, + .index = 0, + }, s.handler.op.?); +} + +test "stream: CSI t pop title with explicit window" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[23;2t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .pop, + .index = 0, + }, s.handler.op.?); +} + +test "stream: CSI t pop title with explicit icon" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[23;1t"); + try testing.expectEqual(null, s.handler.op); +} + +test "stream: CSI t pop title with index" { + const H = struct { + op: ?csi.TitlePushPop = null, + + pub fn pushPopTitle(self: *@This(), op: csi.TitlePushPop) void { + self.op = op; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + + try s.nextSlice("\x1b[23;0;5t"); + try testing.expectEqual(csi.TitlePushPop{ + .op = .pop, + .index = 5, + }, s.handler.op.?); +}