mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
termio/exec: remove a bunch of state that is duplicated on StreamHandler
A lot of the state that we put on Exec is just there to copy to StreamHandler, but we already have it in DerivedConfig. I think this whole copy copy copy is just legacy cruft since termio.Exec is one of the older parts of the source code. This rearchitects the Exec struct to act more like Surface and Renderer where it stores its derived config. This lets us avoid a few extra allocations and removes a LOT of struct member noise from termio.Exec. For pointer lifetimes, the memory allocated is now owned by DerivedConfig. When changeConfig is called, its the only time BOTH are still alive, so we can safely swap pointers and deinit without having to duplicate across threads. This is the same as renderer/surface.
This commit is contained in:
@ -898,7 +898,7 @@ term: []const u8 = "xterm-ghostty",
|
||||
|
||||
/// String to send when we receive ENQ (0x05) from the command that we are
|
||||
/// running. Defaults to "" if not set.
|
||||
@"enquiry-response": ?[]const u8 = null,
|
||||
@"enquiry-response": []const u8 = "",
|
||||
|
||||
/// This is set by the CLI parser for deinit.
|
||||
_arena: ?ArenaAllocator = null,
|
||||
|
@ -44,14 +44,8 @@ alloc: Allocator,
|
||||
/// This is the pty fd created for the subcommand.
|
||||
subprocess: Subprocess,
|
||||
|
||||
/// The number of milliseconds below which we consider a process
|
||||
/// exit to be abnormal. This is used to show an error message
|
||||
/// when the process exits too quickly.
|
||||
abnormal_runtime_threshold_ms: u32,
|
||||
|
||||
/// Equiry string - the string to send back when we receive a ENQ (0x05)
|
||||
/// from the process.
|
||||
enquiry_response: ?[]const u8 = null,
|
||||
/// The derived configuration for this termio implementation.
|
||||
config: DerivedConfig,
|
||||
|
||||
/// The terminal emulator internal state. This is the abstract "terminal"
|
||||
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
||||
@ -74,30 +68,6 @@ surface_mailbox: apprt.surface.Mailbox,
|
||||
/// The cached grid size whenever a resize is called.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
/// The default cursor style. We need to know this so that we can set
|
||||
/// it when a CSI q with default is called.
|
||||
default_cursor_style: terminal.Cursor.Style,
|
||||
default_cursor_blink: ?bool,
|
||||
default_cursor_color: ?terminal.color.RGB,
|
||||
|
||||
/// Actual cursor color
|
||||
cursor_color: ?terminal.color.RGB,
|
||||
|
||||
/// Default foreground color as set by the config file
|
||||
default_foreground_color: terminal.color.RGB,
|
||||
|
||||
/// Default background color as set by the config file
|
||||
default_background_color: terminal.color.RGB,
|
||||
|
||||
/// Actual foreground color
|
||||
foreground_color: terminal.color.RGB,
|
||||
|
||||
/// Actual background color
|
||||
background_color: terminal.color.RGB,
|
||||
|
||||
/// The OSC 10/11 reply style.
|
||||
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
|
||||
|
||||
/// The data associated with the currently running thread.
|
||||
data: ?*EventData,
|
||||
|
||||
@ -105,6 +75,8 @@ data: ?*EventData,
|
||||
/// configuration. This must be exported so that we don't need to
|
||||
/// pass around Config pointers which makes memory management a pain.
|
||||
pub const DerivedConfig = struct {
|
||||
arena: ArenaAllocator,
|
||||
|
||||
palette: terminal.color.Palette,
|
||||
image_storage_limit: usize,
|
||||
cursor_style: terminal.Cursor.Style,
|
||||
@ -122,9 +94,12 @@ pub const DerivedConfig = struct {
|
||||
alloc_gpa: Allocator,
|
||||
config: *const configpkg.Config,
|
||||
) !DerivedConfig {
|
||||
_ = alloc_gpa;
|
||||
var arena = ArenaAllocator.init(alloc_gpa);
|
||||
errdefer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
return .{
|
||||
.arena = arena,
|
||||
.palette = config.palette.value,
|
||||
.image_storage_limit = config.@"image-storage-limit",
|
||||
.cursor_style = config.@"cursor-style",
|
||||
@ -133,25 +108,21 @@ pub const DerivedConfig = struct {
|
||||
.foreground = config.foreground,
|
||||
.background = config.background,
|
||||
.osc_color_report_format = config.@"osc-color-report-format",
|
||||
.term = config.term,
|
||||
.term = try alloc.dupe(u8, config.term),
|
||||
.grapheme_width_method = config.@"grapheme-width-method",
|
||||
.abnormal_runtime_threshold_ms = config.@"abnormal-command-exit-runtime",
|
||||
.enquiry_response = config.@"enquiry-response",
|
||||
.enquiry_response = try alloc.dupe(u8, config.@"enquiry-response"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DerivedConfig) void {
|
||||
_ = self;
|
||||
self.arena.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize the exec implementation. This will also start the child
|
||||
/// process.
|
||||
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
||||
// Clean up our derived config because we don't need it after this.
|
||||
var config = opts.config;
|
||||
defer config.deinit();
|
||||
|
||||
// Create our terminal
|
||||
var term = try terminal.Terminal.init(
|
||||
alloc,
|
||||
@ -194,39 +165,20 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
||||
.alloc = alloc,
|
||||
.terminal = term,
|
||||
.subprocess = subprocess,
|
||||
.config = opts.config,
|
||||
.renderer_state = opts.renderer_state,
|
||||
.renderer_wakeup = opts.renderer_wakeup,
|
||||
.renderer_mailbox = opts.renderer_mailbox,
|
||||
.surface_mailbox = opts.surface_mailbox,
|
||||
.grid_size = opts.grid_size,
|
||||
.default_cursor_style = opts.config.cursor_style,
|
||||
.default_cursor_blink = opts.config.cursor_blink,
|
||||
.default_cursor_color = if (opts.config.cursor_color) |col|
|
||||
col.toTerminalRGB()
|
||||
else
|
||||
null,
|
||||
.cursor_color = if (opts.config.cursor_color) |col|
|
||||
col.toTerminalRGB()
|
||||
else
|
||||
null,
|
||||
.default_foreground_color = config.foreground.toTerminalRGB(),
|
||||
.default_background_color = config.background.toTerminalRGB(),
|
||||
.foreground_color = config.foreground.toTerminalRGB(),
|
||||
.background_color = config.background.toTerminalRGB(),
|
||||
.osc_color_report_format = config.osc_color_report_format,
|
||||
.data = null,
|
||||
.abnormal_runtime_threshold_ms = opts.config.abnormal_runtime_threshold_ms,
|
||||
.enquiry_response = if (opts.config.enquiry_response) |s| try alloc.dupe(u8, s) else null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Exec) void {
|
||||
self.subprocess.deinit();
|
||||
|
||||
// Clean up our other members
|
||||
self.terminal.deinit(self.alloc);
|
||||
|
||||
if (self.enquiry_response) |s| self.alloc.free(s);
|
||||
self.config.deinit();
|
||||
}
|
||||
|
||||
pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
||||
@ -292,24 +244,13 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
||||
.data_stream = stream,
|
||||
.loop = &thread.loop,
|
||||
.terminal_stream = .{
|
||||
.handler = .{
|
||||
.alloc = self.alloc,
|
||||
.ev = ev_data_ptr,
|
||||
.terminal = &self.terminal,
|
||||
.grid_size = &self.grid_size,
|
||||
.default_cursor_style = self.default_cursor_style,
|
||||
.default_cursor_blink = self.default_cursor_blink,
|
||||
.default_cursor_color = self.default_cursor_color,
|
||||
.cursor_color = self.cursor_color,
|
||||
.default_foreground_color = self.default_foreground_color,
|
||||
.default_background_color = self.default_background_color,
|
||||
.foreground_color = self.foreground_color,
|
||||
.background_color = self.background_color,
|
||||
.osc_color_report_format = self.osc_color_report_format,
|
||||
.enquiry_response = if (self.enquiry_response) |s| try self.alloc.dupe(u8, s) else null,
|
||||
.enquiry_response_lock = .{},
|
||||
},
|
||||
|
||||
.handler = StreamHandler.init(
|
||||
self.alloc,
|
||||
ev_data_ptr,
|
||||
&self.grid_size,
|
||||
&self.terminal,
|
||||
&self.config,
|
||||
),
|
||||
.parser = .{
|
||||
.osc_parser = .{
|
||||
// Populate the OSC parser allocator (optional) because
|
||||
@ -318,7 +259,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
||||
},
|
||||
},
|
||||
},
|
||||
.abnormal_runtime_threshold_ms = self.abnormal_runtime_threshold_ms,
|
||||
.abnormal_runtime_threshold_ms = self.config.abnormal_runtime_threshold_ms,
|
||||
};
|
||||
errdefer ev_data_ptr.deinit(self.alloc);
|
||||
|
||||
@ -402,7 +343,21 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
|
||||
|
||||
/// Update the configuration.
|
||||
pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
|
||||
defer config.deinit();
|
||||
// The remainder of this function is modifying terminal state or
|
||||
// the read thread data, all of which requires holding the renderer
|
||||
// state lock.
|
||||
self.renderer_state.mutex.lock();
|
||||
defer self.renderer_state.mutex.unlock();
|
||||
|
||||
// Deinit our old config. We do this in the lock because the
|
||||
// stream handler may be referencing the old config (i.e. enquiry resp)
|
||||
self.config.deinit();
|
||||
self.config = config.*;
|
||||
|
||||
// Update our stream handler. The stream handler uses the same
|
||||
// renderer mutex so this is safe to do despite being executed
|
||||
// from another thread.
|
||||
if (self.data) |data| data.terminal_stream.handler.changeConfig(&self.config);
|
||||
|
||||
// Update the configuration that we know about.
|
||||
//
|
||||
@ -424,32 +379,6 @@ pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// Update our default cursor style
|
||||
self.default_cursor_style = config.cursor_style;
|
||||
self.default_cursor_blink = config.cursor_blink;
|
||||
self.default_cursor_color = if (config.cursor_color) |col|
|
||||
col.toTerminalRGB()
|
||||
else
|
||||
null;
|
||||
|
||||
// Update default foreground and background colors
|
||||
self.default_foreground_color = config.foreground.toTerminalRGB();
|
||||
self.default_background_color = config.background.toTerminalRGB();
|
||||
|
||||
// If we have event data, then update our active stream too
|
||||
if (self.data) |data| {
|
||||
data.terminal_stream.handler.changeDefaultCursor(
|
||||
config.cursor_style,
|
||||
config.cursor_blink,
|
||||
);
|
||||
{
|
||||
data.terminal_stream.handler.enquiry_response_lock.lock();
|
||||
defer data.terminal_stream.handler.enquiry_response_lock.unlock();
|
||||
if (data.terminal_stream.handler.enquiry_response) |s| data.terminal_stream.handler.alloc.free(s);
|
||||
data.terminal_stream.handler.enquiry_response = if (config.enquiry_response) |s| try data.terminal_stream.handler.alloc.dupe(u8, s) else null;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the image size limits
|
||||
try self.terminal.screen.kitty_images.setLimit(
|
||||
self.alloc,
|
||||
@ -1711,14 +1640,60 @@ const StreamHandler = struct {
|
||||
background_color: terminal.color.RGB,
|
||||
|
||||
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
|
||||
enquiry_response: []const u8,
|
||||
|
||||
enquiry_response: ?[]const u8,
|
||||
enquiry_response_lock: std.Thread.RwLock,
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
ev: *EventData,
|
||||
grid_size: *renderer.GridSize,
|
||||
t: *terminal.Terminal,
|
||||
config: *const DerivedConfig,
|
||||
) StreamHandler {
|
||||
const default_cursor_color = if (config.cursor_color) |col|
|
||||
col.toTerminalRGB()
|
||||
else
|
||||
null;
|
||||
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.ev = ev,
|
||||
.grid_size = grid_size,
|
||||
.terminal = t,
|
||||
.osc_color_report_format = config.osc_color_report_format,
|
||||
.enquiry_response = config.enquiry_response orelse "",
|
||||
.default_foreground_color = config.foreground.toTerminalRGB(),
|
||||
.default_background_color = config.background.toTerminalRGB(),
|
||||
.default_cursor_style = config.cursor_style,
|
||||
.default_cursor_blink = config.cursor_blink,
|
||||
.default_cursor_color = default_cursor_color,
|
||||
.cursor_color = default_cursor_color,
|
||||
.foreground_color = config.foreground.toTerminalRGB(),
|
||||
.background_color = config.background.toTerminalRGB(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StreamHandler) void {
|
||||
self.apc.deinit();
|
||||
self.dcs.deinit();
|
||||
if (self.enquiry_response) |s| self.alloc.free(s);
|
||||
}
|
||||
|
||||
/// Change the configuration for this handler.
|
||||
pub fn changeConfig(self: *StreamHandler, config: *DerivedConfig) void {
|
||||
self.osc_color_report_format = config.osc_color_report_format;
|
||||
self.enquiry_response = config.enquiry_response orelse "";
|
||||
self.default_foreground_color = config.foreground.toTerminalRGB();
|
||||
self.default_background_color = config.background.toTerminalRGB();
|
||||
self.default_cursor_style = config.cursor_style;
|
||||
self.default_cursor_blink = config.cursor_blink;
|
||||
self.default_cursor_color = if (config.cursor_color) |col|
|
||||
col.toTerminalRGB()
|
||||
else
|
||||
null;
|
||||
|
||||
// If our cursor is the default, then we update it immediately.
|
||||
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
|
||||
log.warn("failed to set default cursor style: {}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
inline fn queueRender(self: *StreamHandler) !void {
|
||||
@ -1770,21 +1745,6 @@ const StreamHandler = struct {
|
||||
self.writer_messaged = true;
|
||||
}
|
||||
|
||||
pub fn changeDefaultCursor(
|
||||
self: *StreamHandler,
|
||||
style: terminal.Cursor.Style,
|
||||
blink: ?bool,
|
||||
) void {
|
||||
self.default_cursor_style = style;
|
||||
self.default_cursor_blink = blink;
|
||||
|
||||
// If our cursor is the default, then we update it immediately.
|
||||
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
|
||||
log.warn("failed to set default cursor style: {}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dcsHook(self: *StreamHandler, dcs: terminal.DCS) !void {
|
||||
self.dcs.hook(self.alloc, dcs);
|
||||
}
|
||||
@ -2411,14 +2371,8 @@ const StreamHandler = struct {
|
||||
}
|
||||
|
||||
pub fn enquiry(self: *StreamHandler) !void {
|
||||
self.enquiry_response_lock.lockShared();
|
||||
defer self.enquiry_response_lock.unlock();
|
||||
log.debug("sending enquiry response={any}", .{self.enquiry_response orelse ""});
|
||||
if (self.enquiry_response) |r| {
|
||||
self.messageWriter(try termio.Message.writeReq(self.alloc, r));
|
||||
} else {
|
||||
self.messageWriter(.{ .write_stable = "" });
|
||||
}
|
||||
log.debug("sending enquiry response={s}", .{self.enquiry_response});
|
||||
self.messageWriter(try termio.Message.writeReq(self.alloc, self.enquiry_response));
|
||||
}
|
||||
|
||||
pub fn scrollDown(self: *StreamHandler, count: usize) !void {
|
||||
|
Reference in New Issue
Block a user