Skip to main content

tmx/
shell_init.rs

1use anyhow::{Result, bail};
2
3const ZSH_INIT: &str = r#"# tmx shell integration (zsh)
4twt() {
5    emulate -L zsh
6    local sub="${1:-}"
7    case "$sub" in
8        ""|list)
9            # `list --plain` is a text dump → pass through and never cd, even
10            # when only one entry exists (command substitution would strip the
11            # trailing newline, defeating a multi-line heuristic).
12            local arg
13            for arg in "${@:2}"; do
14                if [[ "$arg" == "--plain" ]]; then
15                    command tmx worktree list "${@:2}"
16                    return $?
17                fi
18            done
19            # Picker (or `list <target>`) → cd into the printed path.
20            local out
21            out=$(command tmx worktree list "${@:2}")
22            local rc=$?
23            (( rc != 0 )) && return $rc
24            [[ -z "$out" ]] && return 0
25            cd "$out"
26            ;;
27        rm|create|help|-h|--help)
28            command tmx worktree "$@"
29            ;;
30        *)
31            # Implicit `create <branch>` shorthand. Default flow now spawns
32            # and switches to a tmux session inside tmx itself, so the wrapper
33            # only needs to capture stdout when --keep-current (or -p) is set.
34            local print_only=0 keep_current=0 args=() a
35            for a in "$@"; do
36                case "$a" in
37                    -p|--print)
38                        print_only=1
39                        keep_current=1
40                        ;;
41                    --keep-current)
42                        keep_current=1
43                        args+=("$a")
44                        ;;
45                    *) args+=("$a") ;;
46                esac
47            done
48            if (( keep_current )); then
49                # Ensure --keep-current is passed exactly once even if user
50                # supplied only -p.
51                local has_kc=0 a2
52                for a2 in "${args[@]}"; do
53                    [[ "$a2" == "--keep-current" ]] && has_kc=1
54                done
55                (( has_kc )) || args+=("--keep-current")
56                local out
57                out=$(command tmx worktree create "${args[@]}")
58                local rc=$?
59                (( rc != 0 )) && return $rc
60                [[ -z "$out" ]] && return 0
61                if (( print_only )); then
62                    printf '%s\n' "$out"
63                    return 0
64                fi
65                cd "$out"
66                return 0
67            fi
68            # Default: tmx creates the worktree and switches into a new tmux
69            # session itself; we don't need to cd.
70            command tmx worktree create "${args[@]}"
71            ;;
72    esac
73}
74"#;
75
76pub fn emit(shell: &str) -> Result<&'static str> {
77    match shell {
78        "zsh" => Ok(ZSH_INIT),
79        other => bail!("unsupported shell: {other} (supported: zsh)"),
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn emit_zsh_contains_function_definition() {
89        let out = emit("zsh").unwrap();
90        assert!(out.contains("twt()"));
91        assert!(out.contains("tmx worktree create"));
92        assert!(out.contains("tmx worktree list"));
93        // Subcommands should pass through unchanged, not be re-interpreted as a branch.
94        assert!(out.contains("rm|create|help"));
95        // --plain must short-circuit out of the cd path.
96        assert!(out.contains("--plain"));
97        // --keep-current opt-out and the bare -p alias must be recognised.
98        assert!(out.contains("--keep-current"));
99        assert!(out.contains("-p|--print"));
100    }
101
102    #[test]
103    fn emit_unknown_shell_errors() {
104        let err = emit("fish").unwrap_err();
105        assert!(err.to_string().contains("unsupported shell"));
106        assert!(err.to_string().contains("fish"));
107    }
108}