diff --git a/src/terminal/main.zig b/src/terminal/main.zig index 3de1835d9..23b032daf 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -6,6 +6,7 @@ const ansi = @import("ansi.zig"); const csi = @import("csi.zig"); const sgr = @import("sgr.zig"); pub const apc = @import("apc.zig"); +pub const osc = @import("osc.zig"); pub const point = @import("point.zig"); pub const color = @import("color.zig"); pub const kitty = @import("kitty.zig"); diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index c49ef38a0..03d3baf99 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -95,17 +95,53 @@ pub const Command = union(enum) { /// OSC 10 and OSC 11 default color report. report_default_color: struct { /// OSC 10 requests the foreground color, OSC 11 the background color. - kind: enum { foreground, background }, + kind: DefaultColorKind, /// We must reply with the same string terminator (ST) as used in the /// request. - string_terminator: ?enum { - /// The preferred string terminator is ESC followed by \ - st, - /// Some applications and terminals use BELL (0x07) as the string terminator. - bel, - } = null, + terminator: Terminator = .st, }, + + pub const DefaultColorKind = enum { + foreground, + background, + + pub fn code(self: DefaultColorKind) []const u8 { + return switch (self) { + .foreground => "10", + .background => "11", + }; + } + }; +}; + +/// The terminator used to end an OSC command. For OSC commands that demand +/// a response, we try to match the terminator used in the request since that +/// is most likely to be accepted by the calling program. +pub const Terminator = enum { + /// The preferred string terminator is ESC followed by \ + st, + + /// Some applications and terminals use BELL (0x07) as the string terminator. + bel, + + /// Initialize the terminator based on the last byte seen. If the + /// last byte is a BEL then we use BEL, otherwise we just assume ST. + pub fn init(ch: ?u8) Terminator { + return switch (ch orelse return .st) { + 0x07 => .bel, + else => .st, + }; + } + + /// The terminator as a string. This is static memory so it doesn't + /// need to be freed. + pub fn string(self: Terminator) []const u8 { + return switch (self) { + .st => "\x1b\\", + .bel => "\x07", + }; + } }; pub const Parser = struct { @@ -496,8 +532,10 @@ pub const Parser = struct { } /// End the sequence and return the command, if any. If the return value - /// is null, then no valid command was found. - pub fn end(self: *Parser, string_separator: ?u8) ?Command { + /// 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 { if (!self.complete) { log.warn("invalid OSC command: {s}", .{self.buf[0..self.buf_idx]}); return null; @@ -512,11 +550,10 @@ pub const Parser = struct { } switch (self.command) { - .report_default_color => |*c| { - c.string_terminator = if (string_separator == 0x07) .bel else .st; - }, + .report_default_color => |*c| c.terminator = Terminator.init(terminator_ch), else => {}, } + return self.command; } }; @@ -763,7 +800,7 @@ test "OSC: report default foreground color" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .report_default_color); try testing.expectEqual(cmd.report_default_color.kind, .foreground); - try testing.expectEqual(cmd.report_default_color.string_terminator, .st); + try testing.expectEqual(cmd.report_default_color.terminator, .st); } test "OSC: report default background color" { @@ -778,5 +815,5 @@ test "OSC: report default background color" { const cmd = p.end('\x07').?; try testing.expect(cmd == .report_default_color); try testing.expectEqual(cmd.report_default_color.kind, .background); - try testing.expectEqual(cmd.report_default_color.string_terminator.?, .bel); + try testing.expectEqual(cmd.report_default_color.terminator, .bel); } diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 79d02a8e8..824d08ffa 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -863,10 +863,7 @@ pub fn Stream(comptime Handler: type) type { .report_default_color => |v| { if (@hasDecl(T, "reportDefaultColor")) { - try self.handler.reportDefaultColor( - if (v.kind == .foreground) "10" else "11", - if (v.string_terminator == .st) "\x1b\\" else "\x07", - ); + try self.handler.reportDefaultColor(v.kind, v.terminator); } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 453673761..8dec213d2 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1804,29 +1804,45 @@ const StreamHandler = struct { } } - /// Implements OSC 10 and OSC 11, which reports default foreground and background color respectively. - pub fn reportDefaultColor(self: *StreamHandler, osc_code: []const u8, string_terminator: ?[]const u8) !void { + /// Implements OSC 10 and OSC 11, which reports default foreground and + /// background color respectively. + pub fn reportDefaultColor( + self: *StreamHandler, + kind: terminal.osc.Command.DefaultColorKind, + terminator: terminal.osc.Terminator, + ) !void { if (self.osc_color_report_format == .none) return; + + const color = switch (kind) { + .foreground => self.default_foreground_color, + .background => self.default_background_color, + }; + var msg: termio.Message = .{ .write_small = .{} }; - const color = if (std.mem.eql(u8, osc_code, "10")) self.default_foreground_color else self.default_background_color; - const resp = resp: { - if (self.osc_color_report_format == .bits16) { - break :resp try std.fmt.bufPrint(&msg.write_small.data, "\x1B]{s};rgb:{x:0>4}/{x:0>4}/{x:0>4}{s}", .{ - osc_code, + const resp = switch (self.osc_color_report_format) { + .bits16 => try std.fmt.bufPrint( + &msg.write_small.data, + "\x1B]{s};rgb:{x:0>4}/{x:0>4}/{x:0>4}{s}", + .{ + kind.code(), @as(u16, color.r) * 257, @as(u16, color.g) * 257, @as(u16, color.b) * 257, - if (string_terminator) |st| st else "\x1b\\", - }); - } else { - break :resp try std.fmt.bufPrint(&msg.write_small.data, "\x1B]{s};rgb:{x:0>2}/{x:0>2}/{x:0>2}{s}", .{ - osc_code, + terminator.string(), + }, + ), + + else => try std.fmt.bufPrint( + &msg.write_small.data, + "\x1B]{s};rgb:{x:0>2}/{x:0>2}/{x:0>2}{s}", + .{ + kind.code(), @as(u16, color.r), @as(u16, color.g), @as(u16, color.b), - if (string_terminator) |st| st else "\x1b\\", - }); - } + terminator.string(), + }, + ), }; msg.write_small.len = @intCast(resp.len); self.messageWriter(msg);