diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 44e97e2c7..36cfd7f82 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -301,12 +301,10 @@ pub const Parser = struct { /// Buffer for temporary storage of OSC data buffer: [MAX_BUF]u8, - /// Fixed writer for accumulating OSC data - fixed: ?std.Io.Writer, - /// Allocating writer for accumulating OSC data - allocating: ?std.Io.Writer.Allocating, - /// Pointer to the active writer for accumulating OSC data - writer: ?*std.Io.Writer, + + /// Capture state. If this is set then we're actively capturing the + /// bytes coming into the parser. + capture: ?Capture, /// The command that is the result of parsing. command: Command, @@ -369,9 +367,7 @@ pub const Parser = struct { var result: Parser = .{ .alloc = alloc, .state = .start, - .fixed = null, - .allocating = null, - .writer = null, + .capture = null, .command = .invalid, // Keeping all our undefined values together so we can @@ -394,8 +390,8 @@ pub const Parser = struct { /// Reset the parser state. pub fn reset(self: *Parser) void { - // If we set up an allocating writer, free up that memory. - if (self.allocating) |*allocating| allocating.deinit(); + // If we're capturing, then stop it. + if (self.capture) |*cap| cap.deinit(); // Handle any cleanup that individual OSCs require. switch (self.command) { @@ -430,9 +426,7 @@ pub const Parser = struct { } self.state = .start; - self.fixed = null; - self.allocating = null; - self.writer = null; + self.capture = null; self.command = .invalid; if (std.valgrind.runningOnValgrind() > 0) { @@ -451,31 +445,91 @@ pub const Parser = struct { return false; } - /// Set up a fixed Writer to collect the rest of the OSC data. - inline fn writeToFixed(self: *Parser) void { - self.fixed = .fixed(&self.buffer); - self.writer = &self.fixed.?; - } + const Capture = struct { + writer: *std.Io.Writer, + backing: Backing, - /// Set up an allocating Writer to collect the rest of the OSC data. If we - /// don't have an allocator or setting up the allocator fails, fall back to - /// writing to a fixed buffer and hope that it's big enough. - inline fn writeToAllocating(self: *Parser) void { - const alloc = self.alloc orelse { - // We don't have an allocator - fall back to a fixed buffer and hope - // that it's big enough. - self.writeToFixed(); - return; + const Backing = union(enum) { + fixed: std.Io.Writer, + allocating: std.Io.Writer.Allocating, }; - self.allocating = std.Io.Writer.Allocating.initCapacity(alloc, 2048) catch { - // The allocator failed for some reason, fall back to a fixed buffer - // and hope that it's big enough. - self.writeToFixed(); - return; + const Mode = enum { + fixed, + allocating, }; - self.writer = &self.allocating.?.writer; + pub inline fn fixed(new: *?Capture, buf: []u8) void { + new.* = .{ + .backing = .{ .fixed = .fixed(buf) }, + .writer = &new.*.?.backing.fixed, + }; + } + + pub inline fn allocating( + new: *?Capture, + alloc: Allocator, + ) error{OutOfMemory}!void { + new.* = .{ + .backing = .{ .allocating = try std.Io.Writer.Allocating.initCapacity( + alloc, + 2048, + ) }, + .writer = &new.*.?.backing.allocating.writer, + }; + } + + pub fn deinit(self: *Capture) void { + switch (self.backing) { + .fixed => {}, + .allocating => |*w| w.deinit(), + } + } + + /// Return the captured trailing data. This is the data from the + /// point that trailing data capture was requested. + pub inline fn trailing(self: *Capture) []u8 { + return self.writer.buffered(); + } + }; + + /// Begin capturing trailing data. All inputs to next from this point + /// forward will be captured into the `self.capture.writer` buffer + /// which may be backed by either a fixed size or allocating buffer + /// depending on mode. + /// + /// Get the trailing data using `capture.trailing()`. Do not access + /// the writer directly. + inline fn captureTrailing( + self: *Parser, + comptime mode: Capture.Mode, + ) void { + assert(self.capture == null); + switch (mode) { + .fixed => Capture.fixed( + &self.capture, + &self.buffer, + ), + + .allocating => { + const alloc = self.alloc orelse { + // We don't have an allocator - fall back to a fixed buffer and hope + // that it's big enough. + self.captureTrailing(.fixed); + return; + }; + + Capture.allocating( + &self.capture, + alloc, + ) catch { + // The allocator failed for some reason, fall back to a fixed buffer + // and hope that it's big enough. + self.captureTrailing(.fixed); + return; + }; + }, + } } /// Consume the next character c and advance the parser state. @@ -486,8 +540,8 @@ pub const Parser = struct { // If a writer has been initialized, we just accumulate the rest of the // OSC sequence in the writer's buffer and skip the state machine. - if (self.writer) |writer| { - writer.writeByte(c) catch |err| switch (err) { + if (self.capture) |*cap| { + cap.writer.writeByte(c) catch |err| switch (err) { // We have overflowed our buffer or had some other error, set the // state to invalid so that we discard any further input. error.WriteFailed => self.state = .invalid, @@ -529,12 +583,12 @@ pub const Parser = struct { }, .@"3008" => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), else => self.state = .invalid, }, .@"1" => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), '0' => self.state = .@"10", '1' => self.state = .@"11", '2' => self.state = .@"12", @@ -549,18 +603,18 @@ pub const Parser = struct { }, .@"10" => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), '4' => self.state = .@"104", else => self.state = .invalid, }, .@"104" => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), else => self.state = .invalid, }, .@"11" => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), '0' => self.state = .@"110", '1' => self.state = .@"111", '2' => self.state = .@"112", @@ -594,25 +648,25 @@ pub const Parser = struct { .@"118", .@"119", => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), else => self.state = .invalid, }, .@"13" => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), '3' => self.state = .@"133", else => self.state = .invalid, }, .@"2" => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), '1' => self.state = .@"21", '2' => self.state = .@"22", else => self.state = .invalid, }, .@"5" => switch (c) { - ';' => if (self.ensureAllocator()) self.writeToFixed(), + ';' => if (self.ensureAllocator()) self.captureTrailing(.fixed), '2' => self.state = .@"52", '5' => self.state = .@"55", else => self.state = .invalid, @@ -626,7 +680,7 @@ pub const Parser = struct { .@"52", .@"66", => switch (c) { - ';' => self.writeToAllocating(), + ';' => self.captureTrailing(.allocating), else => self.state = .invalid, }, @@ -636,7 +690,7 @@ pub const Parser = struct { }, .@"7" => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), '7' => self.state = .@"77", else => self.state = .invalid, }, @@ -648,7 +702,7 @@ pub const Parser = struct { .@"133", => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), '7' => self.state = .@"1337", else => self.state = .invalid, }, @@ -660,13 +714,13 @@ pub const Parser = struct { .@"1337", => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), else => self.state = .invalid, }, .@"5522", => switch (c) { - ';' => self.writeToAllocating(), + ';' => self.captureTrailing(.allocating), else => self.state = .invalid, }, @@ -676,7 +730,7 @@ pub const Parser = struct { .@"8", .@"9", => switch (c) { - ';' => self.writeToFixed(), + ';' => self.captureTrailing(.fixed), else => self.state = .invalid, }, } diff --git a/src/terminal/osc/parsers/change_window_icon.zig b/src/terminal/osc/parsers/change_window_icon.zig index aefe17696..eeae6a710 100644 --- a/src/terminal/osc/parsers/change_window_icon.zig +++ b/src/terminal/osc/parsers/change_window_icon.zig @@ -4,15 +4,15 @@ const Command = @import("../../osc.zig").Command; /// Parse OSC 1 pub fn parse(parser: *Parser, _: ?u8) ?*Command { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); parser.command = .{ .change_window_icon = data[0 .. data.len - 1 :0], }; diff --git a/src/terminal/osc/parsers/change_window_title.zig b/src/terminal/osc/parsers/change_window_title.zig index b0bf44dd3..5bd84dc38 100644 --- a/src/terminal/osc/parsers/change_window_title.zig +++ b/src/terminal/osc/parsers/change_window_title.zig @@ -5,15 +5,15 @@ const Command = @import("../../osc.zig").Command; /// Parse OSC 0 and OSC 2 pub fn parse(parser: *Parser, _: ?u8) ?*Command { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); parser.command = .{ .change_window_title = data[0 .. data.len - 1 :0], }; diff --git a/src/terminal/osc/parsers/clipboard_operation.zig b/src/terminal/osc/parsers/clipboard_operation.zig index 59a8831bc..8d149f97c 100644 --- a/src/terminal/osc/parsers/clipboard_operation.zig +++ b/src/terminal/osc/parsers/clipboard_operation.zig @@ -8,15 +8,15 @@ 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 { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); if (data.len == 1) { parser.state = .invalid; return null; diff --git a/src/terminal/osc/parsers/color.zig b/src/terminal/osc/parsers/color.zig index 7d3dc68c0..547c98eaf 100644 --- a/src/terminal/osc/parsers/color.zig +++ b/src/terminal/osc/parsers/color.zig @@ -50,8 +50,8 @@ pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command { // 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(); + const cap = if (parser.capture) |*c| c else break :data ""; + break :data cap.trailing(); }; // Check and make sure that we're parsing the correct OSCs const op: Operation = switch (parser.state) { diff --git a/src/terminal/osc/parsers/context_signal.zig b/src/terminal/osc/parsers/context_signal.zig index c36c76f21..e7eb9348d 100644 --- a/src/terminal/osc/parsers/context_signal.zig +++ b/src/terminal/osc/parsers/context_signal.zig @@ -201,11 +201,11 @@ pub const Field = enum { /// start=[;=]* /// end=[;=]* pub fn parse(parser: *Parser, _: ?u8) ?*OSCCommand { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); if (data.len == 0) { parser.state = .invalid; return null; diff --git a/src/terminal/osc/parsers/hyperlink.zig b/src/terminal/osc/parsers/hyperlink.zig index cf328beb5..d8e84b63c 100644 --- a/src/terminal/osc/parsers/hyperlink.zig +++ b/src/terminal/osc/parsers/hyperlink.zig @@ -7,15 +7,15 @@ const log = std.log.scoped(.osc_hyperlink); /// Parse OSC 8 hyperlinks pub fn parse(parser: *Parser, _: ?u8) ?*Command { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); const s = std.mem.indexOfScalar(u8, data, ';') orelse { parser.state = .invalid; return null; diff --git a/src/terminal/osc/parsers/iterm2.zig b/src/terminal/osc/parsers/iterm2.zig index bd64977cf..b05dd1b2a 100644 --- a/src/terminal/osc/parsers/iterm2.zig +++ b/src/terminal/osc/parsers/iterm2.zig @@ -66,15 +66,15 @@ const map: Map = .initComptime( pub fn parse(parser: *Parser, _: ?u8) ?*Command { assert(parser.state == .@"1337"); - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); const key_str: [:0]u8, const value_: ?[:0]u8 = kv: { const index = std.mem.indexOfScalar(u8, data, '=') orelse { diff --git a/src/terminal/osc/parsers/kitty_clipboard_protocol.zig b/src/terminal/osc/parsers/kitty_clipboard_protocol.zig index 06dec1bf9..5bd0e2547 100644 --- a/src/terminal/osc/parsers/kitty_clipboard_protocol.zig +++ b/src/terminal/osc/parsers/kitty_clipboard_protocol.zig @@ -152,12 +152,12 @@ fn parseIdentifier(str: []const u8) ?[]const u8 { pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command { assert(parser.state == .@"5522"); - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); const metadata: []const u8, const payload: ?[]const u8 = result: { const start = std.mem.indexOfScalar(u8, data, ';') orelse break :result .{ data, null }; diff --git a/src/terminal/osc/parsers/kitty_color.zig b/src/terminal/osc/parsers/kitty_color.zig index 30a7fe77f..c59391ea0 100644 --- a/src/terminal/osc/parsers/kitty_color.zig +++ b/src/terminal/osc/parsers/kitty_color.zig @@ -17,7 +17,7 @@ pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command { parser.state = .invalid; return null; }; - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; @@ -28,7 +28,7 @@ pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command { }, }; const list = &parser.command.kitty_color_protocol.list; - const data = writer.buffered(); + const data = cap.trailing(); var kv_it = std.mem.splitScalar(u8, data, ';'); while (kv_it.next()) |kv| { if (list.items.len >= @as(usize, kitty_color.Kind.max) * 2) { diff --git a/src/terminal/osc/parsers/kitty_text_sizing.zig b/src/terminal/osc/parsers/kitty_text_sizing.zig index 370e22a22..94f271005 100644 --- a/src/terminal/osc/parsers/kitty_text_sizing.zig +++ b/src/terminal/osc/parsers/kitty_text_sizing.zig @@ -71,17 +71,17 @@ pub const OSC = struct { pub fn parse(parser: *Parser, _: ?u8) ?*Command { assert(parser.state == .@"66"); - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; // Write a NUL byte to ensure that `text` is NUL-terminated - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); const payload_start = std.mem.indexOfScalar(u8, data, ';') orelse { log.warn("missing semicolon before payload", .{}); diff --git a/src/terminal/osc/parsers/mouse_shape.zig b/src/terminal/osc/parsers/mouse_shape.zig index 91c5ab270..4b96c038a 100644 --- a/src/terminal/osc/parsers/mouse_shape.zig +++ b/src/terminal/osc/parsers/mouse_shape.zig @@ -8,15 +8,15 @@ 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 { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); parser.command = .{ .mouse_shape = .{ .value = data[0 .. data.len - 1 :0], diff --git a/src/terminal/osc/parsers/osc9.zig b/src/terminal/osc/parsers/osc9.zig index f636813d9..4b8dda9b2 100644 --- a/src/terminal/osc/parsers/osc9.zig +++ b/src/terminal/osc/parsers/osc9.zig @@ -5,15 +5,16 @@ 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 { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; + const writer = cap.writer; // 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(); + var data = cap.trailing(); if (data.len == 0) break :conemu; switch (data[0]) { // Check for OSC 9;1 9;10 9;11 9;12 @@ -90,7 +91,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_comment = data[3 .. data.len - 1 :0], }; @@ -112,7 +113,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_show_message_box = data[2 .. data.len - 1 :0], }; @@ -132,7 +133,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_change_tab_title = .{ .value = data[2 .. data.len - 1 :0], @@ -214,7 +215,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_guimacro = data[2 .. data.len - 1 :0], }; @@ -228,7 +229,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_run_process = data[2 .. data.len - 1 :0], }; @@ -242,7 +243,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .conemu_output_environment_variable = data[2 .. data.len - 1 :0], }; @@ -256,7 +257,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - data = writer.buffered(); + data = cap.trailing(); parser.command = .{ .report_pwd = .{ .value = data[2 .. data.len - 1 :0], @@ -274,7 +275,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); parser.command = .{ .show_desktop_notification = .{ .title = "", diff --git a/src/terminal/osc/parsers/report_pwd.zig b/src/terminal/osc/parsers/report_pwd.zig index 080b9cbb0..f99bc894e 100644 --- a/src/terminal/osc/parsers/report_pwd.zig +++ b/src/terminal/osc/parsers/report_pwd.zig @@ -5,15 +5,15 @@ const Command = @import("../../osc.zig").Command; /// Parse OSC 7 pub fn parse(parser: *Parser, _: ?u8) ?*Command { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); parser.command = .{ .report_pwd = .{ .value = data[0 .. data.len - 1 :0], diff --git a/src/terminal/osc/parsers/rxvt_extension.zig b/src/terminal/osc/parsers/rxvt_extension.zig index 94a0961d2..f5b965c6b 100644 --- a/src/terminal/osc/parsers/rxvt_extension.zig +++ b/src/terminal/osc/parsers/rxvt_extension.zig @@ -7,16 +7,16 @@ const log = std.log.scoped(.osc_rxvt_extension); /// Parse OSC 777 pub fn parse(parser: *Parser, _: ?u8) ?*Command { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; // ensure that we are sentinel terminated - writer.writeByte(0) catch { + cap.writer.writeByte(0) catch { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); const k = std.mem.indexOfScalar(u8, data, ';') orelse { parser.state = .invalid; return null; diff --git a/src/terminal/osc/parsers/semantic_prompt.zig b/src/terminal/osc/parsers/semantic_prompt.zig index c60ce4cb5..25152d7c3 100644 --- a/src/terminal/osc/parsers/semantic_prompt.zig +++ b/src/terminal/osc/parsers/semantic_prompt.zig @@ -298,11 +298,11 @@ pub const Redraw = enum(u2) { /// Parse OSC 133, semantic prompts pub fn parse(parser: *Parser, _: ?u8) ?*OSCCommand { - const writer = parser.writer orelse { + const cap = if (parser.capture) |*c| c else { parser.state = .invalid; return null; }; - const data = writer.buffered(); + const data = cap.trailing(); if (data.len == 0) { parser.state = .invalid; return null;