Merge pull request #1321 from mitchellh/wait

Add `wait-after-command` configuration to keep terminal window open after command exits
This commit is contained in:
Mitchell Hashimoto
2024-01-17 08:51:26 -08:00
committed by GitHub
3 changed files with 47 additions and 6 deletions

View File

@ -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

View File

@ -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.

View File

@ -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 = {},