Skip to main content

verbs/
shell_plan.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Pure shell-hook snippet selection (no FS / stdout I/O).
3//!
4//! Owns the wrapper function bodies emitted by `heddle shell init`.
5//! CLI maps its clap `ShellKind` onto [`ShellHookKind`] and prints the result.
6
7/// Shell families that share a hook snippet shape.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ShellHookKind {
10    Zsh,
11    Bash,
12    Fish,
13}
14
15/// Return the shell-hook snippet for `kind`.
16///
17/// zsh and bash share one function body; fish uses a separate syntax.
18pub fn shell_hook_snippet(kind: ShellHookKind) -> &'static str {
19    match kind {
20        ShellHookKind::Zsh | ShellHookKind::Bash => ZSH_BASH_SNIPPET,
21        ShellHookKind::Fish => FISH_SNIPPET,
22    }
23}
24
25/// zsh + bash share a function shape. The differences (`local`
26/// availability, `${@:N}` slicing) are compatible across both.
27const ZSH_BASH_SNIPPET: &str = r#"# heddle shell hook — installed via `heddle shell init zsh` (or bash)
28# Wraps `heddle start`, `heddle thread switch`, and `heddle thread cd`
29# so they auto-cd into the target thread's worktree.
30# Also defines `__heddle_ps1`, a compact prompt segment helper.
31heddle() {
32    case "$1 $2" in
33        "start "*)
34            local path
35            path=$(command heddle start "${@:2}" --print-cd-path 2>/dev/null) || {
36                command heddle "$@"
37                return $?
38            }
39            cd "$path" && printf 'heddle: %s\n' "$path"
40            ;;
41        "thread switch "*)
42            local path
43            path=$(command heddle thread switch "${@:3}" --print-cd-path 2>/dev/null) || {
44                command heddle "$@"
45                return $?
46            }
47            cd "$path" && printf 'heddle: %s\n' "$path"
48            ;;
49        "thread cd "*)
50            local path
51            path=$(command heddle thread cd "${@:3}") || return $?
52            cd "$path"
53            ;;
54        *)
55            command heddle "$@"
56            ;;
57    esac
58}
59
60__heddle_ps1() {
61    local segment
62    segment=$(command heddle shell prompt 2>/dev/null) || return 0
63    [ -n "$segment" ] && printf '(%s)' "$segment"
64}
65"#;
66
67/// fish uses a different function syntax. Wrappable via `function … end`.
68const FISH_SNIPPET: &str = r#"# heddle shell hook — installed via `heddle shell init fish`
69# Wraps `heddle start`, `heddle thread switch`, and `heddle thread cd`
70# so they auto-cd into the target thread's worktree.
71# Also defines `__heddle_ps1`, a compact prompt segment helper.
72function heddle
73    switch "$argv[1] $argv[2]"
74        case 'start *'
75            set -l path (command heddle start $argv[2..] --print-cd-path 2>/dev/null)
76            if test $status -ne 0
77                command heddle $argv
78                return $status
79            end
80            cd "$path"; and printf 'heddle: %s\n' "$path"
81        case 'thread switch *'
82            set -l path (command heddle thread switch $argv[3..] --print-cd-path 2>/dev/null)
83            if test $status -ne 0
84                command heddle $argv
85                return $status
86            end
87            cd "$path"; and printf 'heddle: %s\n' "$path"
88        case 'thread cd *'
89            set -l path (command heddle thread cd $argv[3..])
90            if test $status -ne 0
91                return $status
92            end
93            cd "$path"
94        case '*'
95            command heddle $argv
96    end
97end
98
99function __heddle_ps1
100    set -l segment (command heddle shell prompt 2>/dev/null)
101    if test -n "$segment"
102        printf '(%s)' "$segment"
103    end
104end
105"#;
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn zsh_and_bash_share_the_same_body() {
113        assert!(std::ptr::eq(
114            shell_hook_snippet(ShellHookKind::Zsh),
115            shell_hook_snippet(ShellHookKind::Bash)
116        ));
117        let body = shell_hook_snippet(ShellHookKind::Zsh);
118        assert!(body.contains("heddle() {"));
119        assert!(body.contains("__heddle_ps1()"));
120    }
121
122    #[test]
123    fn fish_uses_fish_function_syntax() {
124        let body = shell_hook_snippet(ShellHookKind::Fish);
125        assert!(body.contains("function heddle"));
126        assert!(body.contains("$argv"));
127        assert!(body.contains("function __heddle_ps1"));
128    }
129}