mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #683 from mitchellh/osc-alloc
OSC52 can heap-allocate for the payload
This commit is contained in:
@ -218,6 +218,10 @@ pub fn init() Parser {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Parser) void {
|
||||
self.osc_parser.deinit();
|
||||
}
|
||||
|
||||
/// Next consumes the next character c and returns the actions to execute.
|
||||
/// Up to 3 actions may need to be executed -- in order -- representing
|
||||
/// the state exit, transition, and entry actions.
|
||||
|
@ -6,6 +6,8 @@ const osc = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
const log = std.log.scoped(.osc);
|
||||
|
||||
@ -145,6 +147,11 @@ pub const Terminator = enum {
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
/// Optional allocator used to accept data longer than MAX_BUF.
|
||||
/// This only applies to some commands (e.g. OSC 52) that can
|
||||
/// reasonably exceed MAX_BUF.
|
||||
alloc: ?Allocator = null,
|
||||
|
||||
/// Current state of the parser.
|
||||
state: State = .empty,
|
||||
|
||||
@ -156,6 +163,7 @@ pub const Parser = struct {
|
||||
buf: [MAX_BUF]u8 = undefined,
|
||||
buf_start: usize = 0,
|
||||
buf_idx: usize = 0,
|
||||
buf_dynamic: ?*std.ArrayListUnmanaged(u8) = null,
|
||||
|
||||
/// True when a command is complete/valid to return.
|
||||
complete: bool = false,
|
||||
@ -219,14 +227,30 @@ pub const Parser = struct {
|
||||
// Expect a string parameter. param_str must be set as well as
|
||||
// buf_start.
|
||||
string,
|
||||
|
||||
// A string that can grow beyond MAX_BUF. This uses the allocator.
|
||||
// If the parser has no allocator then it is treated as if the
|
||||
// buffer is full.
|
||||
allocable_string,
|
||||
};
|
||||
|
||||
/// This must be called to clean up any allocated memory.
|
||||
pub fn deinit(self: *Parser) void {
|
||||
self.reset();
|
||||
}
|
||||
|
||||
/// Reset the parser start.
|
||||
pub fn reset(self: *Parser) void {
|
||||
self.state = .empty;
|
||||
self.buf_start = 0;
|
||||
self.buf_idx = 0;
|
||||
self.complete = false;
|
||||
if (self.buf_dynamic) |ptr| {
|
||||
const alloc = self.alloc.?;
|
||||
ptr.deinit(alloc);
|
||||
alloc.destroy(ptr);
|
||||
self.buf_dynamic = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the next character c and advance the parser state.
|
||||
@ -351,9 +375,9 @@ pub const Parser = struct {
|
||||
.clipboard_kind => switch (c) {
|
||||
';' => {
|
||||
self.command.clipboard_contents.kind = 'c';
|
||||
self.state = .string;
|
||||
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.prepAllocableString();
|
||||
},
|
||||
else => {
|
||||
self.command.clipboard_contents.kind = c;
|
||||
@ -363,9 +387,9 @@ pub const Parser = struct {
|
||||
|
||||
.clipboard_kind_end => switch (c) {
|
||||
';' => {
|
||||
self.state = .string;
|
||||
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.prepAllocableString();
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
@ -467,10 +491,46 @@ pub const Parser = struct {
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.allocable_string => {
|
||||
const alloc = self.alloc.?;
|
||||
const list = self.buf_dynamic.?;
|
||||
list.append(alloc, c) catch {
|
||||
self.state = .invalid;
|
||||
return;
|
||||
};
|
||||
|
||||
// Never consume buffer space for allocable strings
|
||||
self.buf_idx -= 1;
|
||||
|
||||
// We can complete at any time
|
||||
self.complete = true;
|
||||
},
|
||||
|
||||
.string => self.complete = true,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepAllocableString(self: *Parser) void {
|
||||
assert(self.buf_dynamic == null);
|
||||
|
||||
// We need an allocator. If we don't have an allocator, we
|
||||
// pretend we're just a fixed buffer string and hope we fit!
|
||||
const alloc = self.alloc orelse {
|
||||
self.state = .string;
|
||||
return;
|
||||
};
|
||||
|
||||
// Allocate our dynamic buffer
|
||||
const list = alloc.create(std.ArrayListUnmanaged(u8)) catch {
|
||||
self.state = .string;
|
||||
return;
|
||||
};
|
||||
list.* = .{};
|
||||
|
||||
self.buf_dynamic = list;
|
||||
self.state = .allocable_string;
|
||||
}
|
||||
|
||||
fn endSemanticOptionValue(self: *Parser) void {
|
||||
const value = self.buf[self.buf_start..self.buf_idx];
|
||||
|
||||
@ -531,6 +591,11 @@ pub const Parser = struct {
|
||||
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx];
|
||||
}
|
||||
|
||||
fn endAllocableString(self: *Parser) void {
|
||||
const list = self.buf_dynamic.?;
|
||||
self.temp_state.str.* = list.items;
|
||||
}
|
||||
|
||||
/// End the sequence and return the command, if any. If the return value
|
||||
/// 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
|
||||
@ -546,6 +611,7 @@ pub const Parser = struct {
|
||||
.semantic_exit_code => self.endSemanticExitCode(),
|
||||
.semantic_option_value => self.endSemanticOptionValue(),
|
||||
.string => self.endString(),
|
||||
.allocable_string => self.endAllocableString(),
|
||||
else => {},
|
||||
}
|
||||
|
||||
@ -762,6 +828,22 @@ test "OSC: get/set clipboard (optional parameter)" {
|
||||
try testing.expect(std.mem.eql(u8, "?", cmd.clipboard_contents.data));
|
||||
}
|
||||
|
||||
test "OSC: get/set clipboard with allocator" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var p: Parser = .{ .alloc = alloc };
|
||||
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.expect(std.mem.eql(u8, "?", cmd.clipboard_contents.data));
|
||||
}
|
||||
|
||||
test "OSC: report pwd" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
@ -38,6 +38,10 @@ pub fn Stream(comptime Handler: type) type {
|
||||
handler: Handler,
|
||||
parser: Parser = .{},
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.parser.deinit();
|
||||
}
|
||||
|
||||
/// Process a string of characters.
|
||||
pub fn nextSlice(self: *Self, c: []const u8) !void {
|
||||
const tracy = trace(@src());
|
||||
|
@ -229,6 +229,14 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
||||
.default_background_color = self.default_background_color,
|
||||
.osc_color_report_format = self.osc_color_report_format,
|
||||
},
|
||||
|
||||
.parser = .{
|
||||
.osc_parser = .{
|
||||
// Populate the OSC parser allocator (optional) because
|
||||
// we want to support large OSC payloads such as OSC 52.
|
||||
.alloc = self.alloc,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
errdefer ev_data_ptr.deinit(self.alloc);
|
||||
@ -565,6 +573,7 @@ const EventData = struct {
|
||||
|
||||
// Clear any StreamHandler state
|
||||
self.terminal_stream.handler.deinit();
|
||||
self.terminal_stream.deinit();
|
||||
}
|
||||
|
||||
/// This queues a render operation with the renderer thread. The render
|
||||
|
Reference in New Issue
Block a user