aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/commands/init.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/atuin-ai/src/commands/init.rs')
-rw-r--r--crates/atuin-ai/src/commands/init.rs155
1 files changed, 152 insertions, 3 deletions
diff --git a/crates/atuin-ai/src/commands/init.rs b/crates/atuin-ai/src/commands/init.rs
index bf5c6256..8174b583 100644
--- a/crates/atuin-ai/src/commands/init.rs
+++ b/crates/atuin-ai/src/commands/init.rs
@@ -1,9 +1,29 @@
-pub async fn run() -> eyre::Result<()> {
- let zsh_function = generate_zsh_integration();
- println!("{}", zsh_function);
+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#"
@@ -53,6 +73,111 @@ bindkey '?' _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 --natural-language 3>&1 1>&2 2>&3)
+
+ if [[ $output == __atuin_ai_cancel__ ]]; then
+ # User cancelled, do nothing
+ 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 --natural-language 3>&1 1>&2 2>&3 | string collect)
+
+ if test "$output" = "__atuin_ai_cancel__"
+ # User cancelled, do nothing
+ 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::*;
@@ -67,4 +192,28 @@ mod tests {
assert!(result.contains("__atuin_ai_execute__"));
assert!(result.contains("__atuin_ai_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"));
+ 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"));
+ assert!(result.contains("__atuin_ai_cancel__"));
+ assert!(result.contains("__atuin_ai_execute__"));
+ assert!(result.contains("__atuin_ai_insert__"));
+ }
}