git_worktree_manager/operations/launchers/mod.rs
1/// Terminal launcher implementations.
2pub mod detached;
3pub mod foreground;
4pub mod iterm;
5pub mod tmux;
6pub mod wezterm;
7pub mod zellij;
8
9/// Wrap a command line so that, after it exits, the pane/window/tab keeps a
10/// fresh interactive login shell instead of closing.
11///
12/// The tmux and zellij launchers (session, window/tab, panes) run the command
13/// *as* the pane's program (`bash -lc <cmd>`), so when the AI tool exits the
14/// pane/tab/session closes with it — you lose the worktree context and can't
15/// run follow-up commands. WezTerm and iTerm don't have this problem because
16/// they type the command into an already-running shell; tmux's own session
17/// launcher sidesteps it by `send-keys`-ing into a pre-spawned shell.
18///
19/// Appending `; exec "${SHELL:-bash}" -l` makes the affected launchers behave
20/// the same way: the command runs, then — regardless of its exit code (`;`,
21/// not `&&`) — control drops to a fresh login shell in the same cwd. A
22/// non-zero exit leaves the user at a prompt with the context intact rather
23/// than closing the pane.
24///
25/// `${SHELL:-bash}` honors the user's login shell when the env var is present
26/// (the common case when `gw` is run from an interactive session) and falls
27/// back to `bash` otherwise. The result is still meant to be passed to
28/// `bash -lc`, so `<cmd>` keeps whatever quoting it already carries.
29pub fn keep_shell_after(command: &str) -> String {
30 format!("{command}; exec \"${{SHELL:-bash}}\" -l")
31}
32
33#[cfg(test)]
34mod tests {
35 use super::keep_shell_after;
36
37 #[test]
38 fn appends_exec_login_shell() {
39 assert_eq!(
40 keep_shell_after("/usr/local/bin/gw _spawn-ai /tmp/x.json"),
41 "/usr/local/bin/gw _spawn-ai /tmp/x.json; exec \"${SHELL:-bash}\" -l"
42 );
43 }
44
45 #[test]
46 fn preserves_existing_quoting() {
47 // spawn_spec emits quoted segments when the path has spaces; we must
48 // not disturb them — the whole thing is still handed to `bash -lc`.
49 let cmd = r#""/My App/gw" _spawn-ai "/tmp/x y.json""#;
50 assert_eq!(
51 keep_shell_after(cmd),
52 r#""/My App/gw" _spawn-ai "/tmp/x y.json"; exec "${SHELL:-bash}" -l"#
53 );
54 }
55}