From 7801e97127d3b1795b0d72cb721aba48c0fa2c16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2026 13:12:38 -0700 Subject: [PATCH] terminal: redo trailing state capture in OSC parser Trailing state capture now is encapsulated in a struct `Capture` and all parsers access the data via `p.capture.trailing()` rather than directly from the writer. This is primarily to prep for the OSC parser to be able to capture the entire sequence (not just the trailing part) so we can setup libghostty for fallback handlers so libghostty implementers can have custom OSC behaviors. But, it has the benefit of making our OSC parser much cleaner too. --- src/terminal/osc.zig | 156 ++++++++++++------ .../osc/parsers/change_window_icon.zig | 6 +- .../osc/parsers/change_window_title.zig | 6 +- .../osc/parsers/clipboard_operation.zig | 6 +- src/terminal/osc/parsers/color.zig | 4 +- src/terminal/osc/parsers/context_signal.zig | 4 +- src/terminal/osc/parsers/hyperlink.zig | 6 +- src/terminal/osc/parsers/iterm2.zig | 6 +- .../osc/parsers/kitty_clipboard_protocol.zig | 4 +- src/terminal/osc/parsers/kitty_color.zig | 4 +- .../osc/parsers/kitty_text_sizing.zig | 6 +- src/terminal/osc/parsers/mouse_shape.zig | 6 +- src/terminal/osc/parsers/osc9.zig | 21 +-- src/terminal/osc/parsers/report_pwd.zig | 6 +- src/terminal/osc/parsers/rxvt_extension.zig | 6 +- src/terminal/osc/parsers/semantic_prompt.zig | 4 +- 16 files changed, 153 insertions(+), 98 deletions(-) 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;