From 8f0be3ad9efd289f0bc27422fcd2bebd1e7f5bdd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 19 Mar 2023 10:09:17 -0700 Subject: [PATCH] termio: use DerivedConfig --- src/Surface.zig | 18 +++++++++++++--- src/termio/Exec.zig | 49 +++++++++++++++++++++++++++++++++++++++--- src/termio/Options.zig | 8 +++++-- src/termio/Thread.zig | 1 + src/termio/message.zig | 4 ++++ 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index f6307c696..cb8e1fe49 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -362,7 +362,8 @@ pub fn init( var io = try termio.Impl.init(alloc, .{ .grid_size = grid_size, .screen_size = screen_size, - .config = config, + .full_config = config, + .config = try termio.Impl.DerivedConfig.init(alloc, config), .renderer_state = &self.renderer_state, .renderer_wakeup = render_thread.wakeup, .renderer_mailbox = render_thread.mailbox, @@ -572,11 +573,22 @@ fn changeConfig(self: *Surface, config: *const configpkg.Config) !void { // then send them a message to update. var renderer_config = try Renderer.DerivedConfig.init(self.alloc, config); errdefer renderer_config.deinit(); - // TODO: termio config - + var termio_config = try termio.Impl.DerivedConfig.init(self.alloc, config); + errdefer termio_config.deinit(); _ = self.renderer_thread.mailbox.push(.{ .change_config = renderer_config, }, .{ .forever = {} }); + _ = self.io_thread.mailbox.push(.{ + .change_config = termio_config, + }, .{ .forever = {} }); + + // With mailbox messages sent, we have to wake them up so they process it. + self.queueRender() catch |err| { + log.warn("failed to notify renderer of config change err={}", .{err}); + }; + self.io_thread.wakeup.notify() catch |err| { + log.warn("failed to notify io thread of config change err={}", .{err}); + }; } /// Returns the x/y coordinate of where the IME (Input Method Editor) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index c1a903b7f..515a66c25 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -6,6 +6,7 @@ const builtin = @import("builtin"); const build_config = @import("../build_config.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; const EnvMap = std.process.EnvMap; const termio = @import("../termio.zig"); const Command = @import("../Command.zig"); @@ -19,6 +20,7 @@ const trace = tracy.trace; const apprt = @import("../apprt.zig"); const fastmem = @import("../fastmem.zig"); const internal_os = @import("../os/main.zig"); +const configpkg = @import("../config.zig"); const log = std.log.scoped(.io_exec); @@ -62,9 +64,35 @@ grid_size: renderer.GridSize, /// The data associated with the currently running thread. data: ?*EventData, +/// The configuration for this IO that is derived from the main +/// 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 { + palette: terminal.color.Palette, + + pub fn init( + alloc_gpa: Allocator, + config: *const configpkg.Config, + ) !DerivedConfig { + _ = alloc_gpa; + + return .{ + .palette = config.palette.value, + }; + } + + pub fn deinit(self: *DerivedConfig) void { + _ = self; + } +}; + /// 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, @@ -72,7 +100,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec { opts.grid_size.rows, ); errdefer term.deinit(alloc); - term.color_palette = opts.config.palette.value; + term.color_palette = opts.config.palette; var subprocess = try Subprocess.init(alloc, opts); errdefer subprocess.deinit(); @@ -189,6 +217,14 @@ pub fn threadExit(self: *Exec, data: ThreadData) void { data.read_thread.join(); } +/// Update the configuration. +pub fn changeConfig(self: *Exec, config: DerivedConfig) !void { + var copy = config; + defer copy.deinit(); + + self.terminal.color_palette = config.palette; +} + /// Resize the terminal. pub fn resize( self: *Exec, @@ -413,7 +449,7 @@ const Subprocess = struct { const alloc = arena.allocator(); // Determine the path to the binary we're executing - const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse + const path = (try Command.expandPath(alloc, opts.full_config.command orelse "sh")) orelse return error.CommandNotFound; // On macOS, we launch the program as a login shell. This is a Mac-specific @@ -487,10 +523,17 @@ const Subprocess = struct { break :args try args.toOwnedSlice(); }; + // We have to copy the cwd because there is no guarantee that + // pointers in full_config remain valid. + var cwd: ?[]u8 = if (opts.full_config.@"working-directory") |cwd| + try alloc.dupe(u8, cwd) + else + null; + return .{ .arena = arena, .env = env, - .cwd = opts.config.@"working-directory", + .cwd = cwd, .path = if (internal_os.isFlatpak()) args[0] else path, .args = args, .grid_size = opts.grid_size, diff --git a/src/termio/Options.zig b/src/termio/Options.zig index 28a10f1f4..5e88b1010 100644 --- a/src/termio/Options.zig +++ b/src/termio/Options.zig @@ -4,6 +4,7 @@ const xev = @import("xev"); const apprt = @import("../apprt.zig"); const renderer = @import("../renderer.zig"); const Config = @import("../config.zig").Config; +const termio = @import("../termio.zig"); /// The size of the terminal grid. grid_size: renderer.GridSize, @@ -11,10 +12,13 @@ grid_size: renderer.GridSize, /// The size of the viewport in pixels. screen_size: renderer.ScreenSize, -/// The app configuration. This must NOT be stored by any termio implementation. +/// The full app configuration. This is only available during initialization. /// The memory it points to is NOT stable after the init call so any values /// in here must be copied. -config: *const Config, +full_config: *const Config, + +/// The derived configuration for this termio implementation. +config: termio.Impl.DerivedConfig, /// The render state. The IO implementation can modify anything here. The /// surface thread will setup the initial "terminal" pointer but the IO impl diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index b1c03cc6e..72656f00e 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -150,6 +150,7 @@ fn drainMailbox(self: *Thread) !void { log.debug("mailbox message={}", .{message}); switch (message) { + .change_config => |config| try self.impl.changeConfig(config), .resize => |v| self.handleResize(v), .clear_screen => |v| try self.impl.clearScreen(v.history), .write_small => |v| try self.impl.queueWrite(v.data[0..v.len]), diff --git a/src/termio/message.zig b/src/termio/message.zig index d1a75bc01..5399b3d9d 100644 --- a/src/termio/message.zig +++ b/src/termio/message.zig @@ -3,6 +3,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const renderer = @import("../renderer.zig"); const terminal = @import("../terminal/main.zig"); +const termio = @import("../termio.zig"); /// The messages that can be sent to an IO thread. /// @@ -28,6 +29,9 @@ pub const Message = union(enum) { padding: renderer.Padding, }; + /// The derived configuration to update the implementation with. + change_config: termio.Impl.DerivedConfig, + /// Resize the window. resize: Resize,