use crate::commands::detect_shell; pub async fn run(shell: String) -> eyre::Result<()> { let integration = match shell.as_str() { "zsh" => generate_zsh_integration(), "bash" => generate_bash_integration(), "fish" => generate_fish_integration(), "auto" => generate_auto_integration()?, _ => eyre::bail!("Unsupported shell: {}", shell), }; println!("{}", integration); Ok(()) } fn generate_auto_integration() -> eyre::Result<&'static str> { let shell = detect_shell(); match shell.as_deref() { Some("zsh") => Ok(generate_zsh_integration()), Some("bash") => Ok(generate_bash_integration()), Some("fish") => Ok(generate_fish_integration()), Some(s) => eyre::bail!("Unsupported shell: {}", s), None => eyre::bail!("Could not detect shell"), } } /// Generate the zsh integration function - pure function for easy testing pub fn generate_zsh_integration() -> &'static str { r#" # TUI uses an alternate screen, so no explicit cleanup is needed. _atuin_ai_cleanup() { true } # Question mark at start of line - natural language mode. # Named with 'self-' prefix so bracketed-paste-magic activates it during # paste, allowing url-quote-magic to escape ? in pasted URLs via self-insert. self-atuin-ai-question-mark() { # If buffer is empty or just contains '?', trigger natural language mode if [[ -z "$BUFFER" || "$BUFFER" == "?" ]]; then BUFFER="" local output output=$(atuin ai inline --hook 3>&1 1>&2 2>&3) # Clean up the inline viewport _atuin_ai_cleanup if [[ $output == __atuin_ai_print__:* ]]; then zle -I echo "${output#__atuin_ai_print__:}" elif [[ $output == __atuin_ai_cancel__ ]]; then zle reset-prompt elif [[ $output == __atuin_ai_execute__:* ]]; then RBUFFER="" LBUFFER=${output#__atuin_ai_execute__:} zle reset-prompt zle accept-line elif [[ $output == __atuin_ai_insert__:* ]]; then RBUFFER="" LBUFFER=${output#__atuin_ai_insert__:} zle reset-prompt elif [[ -n $output ]]; then RBUFFER="" LBUFFER=$output zle reset-prompt else zle reset-prompt fi else zle self-insert fi } # Set up keybindings zle -N self-atuin-ai-question-mark bindkey '?' self-atuin-ai-question-mark # Question mark "# .trim() } /// Generate the bash integration function - pure function for easy testing pub fn generate_bash_integration() -> &'static str { r#" # Question mark at start of line - natural language mode _atuin_ai_question_mark() { # If buffer is empty or just contains '?', trigger natural language mode if [[ -z "$READLINE_LINE" || "$READLINE_LINE" == "?" ]]; then READLINE_LINE="" READLINE_POINT=0 local output output=$(atuin ai inline --hook 3>&1 1>&2 2>&3) if [[ $output == __atuin_ai_print__:* ]]; then echo "${output#__atuin_ai_print__:}" READLINE_LINE="" READLINE_POINT=0 elif [[ $output == __atuin_ai_cancel__ ]]; then READLINE_LINE="" READLINE_POINT=0 elif [[ $output == __atuin_ai_execute__:* ]]; then # Execute the command immediately READLINE_LINE=${output#__atuin_ai_execute__:} READLINE_POINT=${#READLINE_LINE} # Note: We can't directly execute in bash bind -x, but we can # use a workaround by binding to a macro that accepts the line bind '"\C-x\C-a": accept-line' bind -x '"\C-x\C-e": _atuin_ai_question_mark' elif [[ $output == __atuin_ai_insert__:* ]]; then # Insert the command for editing READLINE_LINE=${output#__atuin_ai_insert__:} READLINE_POINT=${#READLINE_LINE} elif [[ -n $output ]]; then # Default: insert for editing READLINE_LINE=$output READLINE_POINT=${#READLINE_LINE} fi else # Not at empty prompt, just insert the question mark READLINE_LINE="${READLINE_LINE:0:READLINE_POINT}?${READLINE_LINE:READLINE_POINT}" ((READLINE_POINT++)) fi } # Set up keybindings # Bash requires special handling: we use bind -x for the function, # but need a two-step approach for execute mode __atuin_ai_accept_line="" _atuin_ai_question_mark_wrapper() { _atuin_ai_question_mark if [[ -n "$__atuin_ai_accept_line" ]]; then __atuin_ai_accept_line="" fi } bind -x '"?": _atuin_ai_question_mark' "# .trim() } /// Generate the fish integration function - pure function for easy testing pub fn generate_fish_integration() -> &'static str { r#" # Question mark at start of line - natural language mode function _atuin_ai_question_mark set -l buf (commandline -b) # If buffer is empty or just contains '?', trigger natural language mode if test -z "$buf" -o "$buf" = "?" commandline -r "" # Run atuin ai inline, swapping stdout and stderr set -l output (atuin ai inline --hook 3>&1 1>&2 2>&3 | string collect) if string match --quiet '__atuin_ai_print__:*' "$output" echo (string replace "__atuin_ai_print__:" "" -- "$output" | string collect) commandline -f repaint else if test "$output" = "__atuin_ai_cancel__" commandline -f repaint else if string match --quiet '__atuin_ai_execute__:*' "$output" # Execute the command immediately set -l cmd (string replace "__atuin_ai_execute__:" "" -- "$output" | string collect) commandline -r "$cmd" commandline -f repaint commandline -f execute else if string match --quiet '__atuin_ai_insert__:*' "$output" # Insert the command for editing set -l cmd (string replace "__atuin_ai_insert__:" "" -- "$output" | string collect) commandline -r "$cmd" commandline -f repaint else if test -n "$output" # Default: insert for editing commandline -r "$output" commandline -f repaint else commandline -f repaint end else # Not at empty prompt, just insert the question mark commandline -i "?" end end # Set up keybindings bind "?" _atuin_ai_question_mark "# .trim() } #[cfg(test)] mod tests { use super::*; #[test] fn test_generate_zsh_integration() { let result = generate_zsh_integration(); assert!(result.contains("self-atuin-ai-question-mark")); assert!(result.contains("bindkey")); assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_print__")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); assert!(result.contains("zle self-insert")); } #[test] fn test_generate_bash_integration() { let result = generate_bash_integration(); assert!(result.contains("_atuin_ai_question_mark")); assert!(result.contains("bind")); assert!(result.contains("READLINE_LINE")); assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_print__")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); } #[test] fn test_generate_fish_integration() { let result = generate_fish_integration(); assert!(result.contains("_atuin_ai_question_mark")); assert!(result.contains("bind")); assert!(result.contains("commandline")); assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_print__")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); } }