mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +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 .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Parser) void {
|
||||||
|
self.osc_parser.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
/// Next consumes the next character c and returns the actions to execute.
|
/// Next consumes the next character c and returns the actions to execute.
|
||||||
/// Up to 3 actions may need to be executed -- in order -- representing
|
/// Up to 3 actions may need to be executed -- in order -- representing
|
||||||
/// the state exit, transition, and entry actions.
|
/// the state exit, transition, and entry actions.
|
||||||
|
@ -6,6 +6,8 @@ const osc = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
const log = std.log.scoped(.osc);
|
const log = std.log.scoped(.osc);
|
||||||
|
|
||||||
@ -145,6 +147,11 @@ pub const Terminator = enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Parser = struct {
|
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.
|
/// Current state of the parser.
|
||||||
state: State = .empty,
|
state: State = .empty,
|
||||||
|
|
||||||
@ -156,6 +163,7 @@ pub const Parser = struct {
|
|||||||
buf: [MAX_BUF]u8 = undefined,
|
buf: [MAX_BUF]u8 = undefined,
|
||||||
buf_start: usize = 0,
|
buf_start: usize = 0,
|
||||||
buf_idx: usize = 0,
|
buf_idx: usize = 0,
|
||||||
|
buf_dynamic: ?*std.ArrayListUnmanaged(u8) = null,
|
||||||
|
|
||||||
/// True when a command is complete/valid to return.
|
/// True when a command is complete/valid to return.
|
||||||
complete: bool = false,
|
complete: bool = false,
|
||||||
@ -219,14 +227,30 @@ pub const Parser = struct {
|
|||||||
// Expect a string parameter. param_str must be set as well as
|
// Expect a string parameter. param_str must be set as well as
|
||||||
// buf_start.
|
// buf_start.
|
||||||
string,
|
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.
|
/// Reset the parser start.
|
||||||
pub fn reset(self: *Parser) void {
|
pub fn reset(self: *Parser) void {
|
||||||
self.state = .empty;
|
self.state = .empty;
|
||||||
self.buf_start = 0;
|
self.buf_start = 0;
|
||||||
self.buf_idx = 0;
|
self.buf_idx = 0;
|
||||||
self.complete = false;
|
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.
|
/// Consume the next character c and advance the parser state.
|
||||||
@ -351,9 +375,9 @@ pub const Parser = struct {
|
|||||||
.clipboard_kind => switch (c) {
|
.clipboard_kind => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.command.clipboard_contents.kind = 'c';
|
self.command.clipboard_contents.kind = 'c';
|
||||||
self.state = .string;
|
|
||||||
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
||||||
self.buf_start = self.buf_idx;
|
self.buf_start = self.buf_idx;
|
||||||
|
self.prepAllocableString();
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
self.command.clipboard_contents.kind = c;
|
self.command.clipboard_contents.kind = c;
|
||||||
@ -363,9 +387,9 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
.clipboard_kind_end => switch (c) {
|
.clipboard_kind_end => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.state = .string;
|
|
||||||
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
self.temp_state = .{ .str = &self.command.clipboard_contents.data };
|
||||||
self.buf_start = self.buf_idx;
|
self.buf_start = self.buf_idx;
|
||||||
|
self.prepAllocableString();
|
||||||
},
|
},
|
||||||
else => self.state = .invalid,
|
else => self.state = .invalid,
|
||||||
},
|
},
|
||||||
@ -467,10 +491,46 @@ pub const Parser = struct {
|
|||||||
else => self.state = .invalid,
|
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,
|
.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 {
|
fn endSemanticOptionValue(self: *Parser) void {
|
||||||
const value = self.buf[self.buf_start..self.buf_idx];
|
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];
|
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
|
/// 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 null, then no valid command was found. The optional terminator_ch
|
||||||
/// is the final character in the OSC sequence. This is used to determine
|
/// 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_exit_code => self.endSemanticExitCode(),
|
||||||
.semantic_option_value => self.endSemanticOptionValue(),
|
.semantic_option_value => self.endSemanticOptionValue(),
|
||||||
.string => self.endString(),
|
.string => self.endString(),
|
||||||
|
.allocable_string => self.endAllocableString(),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -762,6 +828,22 @@ test "OSC: get/set clipboard (optional parameter)" {
|
|||||||
try testing.expect(std.mem.eql(u8, "?", cmd.clipboard_contents.data));
|
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" {
|
test "OSC: report pwd" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -38,6 +38,10 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
handler: Handler,
|
handler: Handler,
|
||||||
parser: Parser = .{},
|
parser: Parser = .{},
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.parser.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a string of characters.
|
/// Process a string of characters.
|
||||||
pub fn nextSlice(self: *Self, c: []const u8) !void {
|
pub fn nextSlice(self: *Self, c: []const u8) !void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
|
@ -229,6 +229,14 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
.default_background_color = self.default_background_color,
|
.default_background_color = self.default_background_color,
|
||||||
.osc_color_report_format = self.osc_color_report_format,
|
.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);
|
errdefer ev_data_ptr.deinit(self.alloc);
|
||||||
@ -565,6 +573,7 @@ const EventData = struct {
|
|||||||
|
|
||||||
// Clear any StreamHandler state
|
// Clear any StreamHandler state
|
||||||
self.terminal_stream.handler.deinit();
|
self.terminal_stream.handler.deinit();
|
||||||
|
self.terminal_stream.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This queues a render operation with the renderer thread. The render
|
/// This queues a render operation with the renderer thread. The render
|
||||||
|
Reference in New Issue
Block a user