mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
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:
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 = {},
|
||||
|
Reference in New Issue
Block a user