From 33bc424d7edc56128f803273221fbf05654cbf39 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 17 Jan 2024 08:40:21 -0800 Subject: [PATCH 1/2] config: introduce wait-after-command --- src/config/Config.zig | 8 ++++++++ src/termio/Exec.zig | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 606a271e0..e323d5ce9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -400,6 +400,14 @@ palette: Palette = .{}, /// flag. For example: `ghostty -e fish --with --custom --args`. command: ?[]const u8 = null, +/// If true, keep the terminal open after the command exits. Normally, +/// the terminal window closes when the running command (such as a shell) +/// exits. With this true, the terminal window will stay open until any +/// keypress is received. +/// +/// This is primarily useful for scripts or debugging. +@"wait-after-command": bool = false, + /// The number of milliseconds of runtime below which we consider a process /// exit to be abnormal. This is used to show an error message when the /// process exits too quickly. diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index a7196948c..a90df93a8 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -86,6 +86,7 @@ pub const DerivedConfig = struct { term: []const u8, grapheme_width_method: configpkg.Config.GraphemeWidthMethod, abnormal_runtime_threshold_ms: u32, + wait_after_command: bool, enquiry_response: []const u8, pub fn init( @@ -108,6 +109,7 @@ pub const DerivedConfig = struct { .term = try alloc.dupe(u8, config.term), .grapheme_width_method = config.@"grapheme-width-method", .abnormal_runtime_threshold_ms = config.@"abnormal-command-exit-runtime", + .wait_after_command = config.@"wait-after-command", .enquiry_response = try alloc.dupe(u8, config.@"enquiry-response"), // This has to be last so that we copy AFTER the arena allocations @@ -261,6 +263,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData { }, }, .abnormal_runtime_threshold_ms = self.config.abnormal_runtime_threshold_ms, + .wait_after_command = self.config.wait_after_command, }; errdefer ev_data_ptr.deinit(self.alloc); @@ -358,7 +361,11 @@ pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void { // 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); + if (self.data) |data| { + data.abnormal_runtime_threshold_ms = config.abnormal_runtime_threshold_ms; + data.wait_after_command = config.wait_after_command; + data.terminal_stream.handler.changeConfig(&self.config); + } // Update the configuration that we know about. // @@ -722,6 +729,10 @@ const EventData = struct { /// when the process exits too quickly. abnormal_runtime_threshold_ms: u32, + /// If true, do not immediately send a child exited message to the + /// surface to close the surface when the command exits. + wait_after_command: bool, + pub fn deinit(self: *EventData, alloc: Allocator) void { // Clear our write pools. We know we aren't ever going to do // any more IO since we stop our data stream below so we can just @@ -803,6 +814,26 @@ fn processExit( return .disarm; } + // If we're purposely waiting then we just return since the process + // exited flag is set to true. This allows the terminal window to remain + // open. + if (ev.wait_after_command) { + // We output a message so that the user knows whats going on and + // doesn't think their terminal just froze. + terminal: { + ev.renderer_state.mutex.lock(); + defer ev.renderer_state.mutex.unlock(); + const t = ev.renderer_state.terminal; + t.carriageReturn(); + t.linefeed() catch break :terminal; + t.printString("Process exited. Press any key to close the terminal.") catch + break :terminal; + t.modes.set(.cursor_visible, false); + } + + return .disarm; + } + // Notify our surface we want to close _ = ev.surface_mailbox.push(.{ .child_exited = {}, From 93c87f21ffb134d71fa5edee963b898dcfd83c58 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 17 Jan 2024 08:43:04 -0800 Subject: [PATCH 2/2] apprt/embedded: set wait-after-command when command is set --- src/apprt/embedded.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index c5592b377..ed27fe8b8 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -258,7 +258,9 @@ pub const Surface = struct { /// The working directory to load into. working_directory: [*:0]const u8 = "", - /// The command to run in the new surface. + /// The command to run in the new surface. If this is set then + /// the "wait-after-command" option is also automatically set to true, + /// since this is used for scripting. command: [*:0]const u8 = "", }; @@ -334,10 +336,10 @@ pub const Surface = struct { } // If we have a command from the options then we set it. - const cm = std.mem.sliceTo(opts.command, 0); - if (cm.len > 0) { - // TODO: Maybe add some validation to this, like the working directory has? - config.command = cm; + const cmd = std.mem.sliceTo(opts.command, 0); + if (cmd.len > 0) { + config.command = cmd; + config.@"wait-after-command" = true; } // Initialize our surface right away. We're given a view that is