syncable_cli/agent/ui/
autocomplete.rs

1//! Autocomplete support for slash commands using inquire
2//!
3//! Provides a custom Autocomplete implementation that shows
4//! slash command suggestions as the user types.
5
6use inquire::autocompletion::{Autocomplete, Replacement};
7use crate::agent::commands::SLASH_COMMANDS;
8
9/// Autocomplete provider for slash commands
10/// Shows suggestions when user types "/" followed by characters
11#[derive(Clone, Default)]
12pub struct SlashCommandAutocomplete {
13    /// Cache of filtered commands for current input
14    filtered_commands: Vec<&'static str>,
15}
16
17impl SlashCommandAutocomplete {
18    pub fn new() -> Self {
19        Self {
20            filtered_commands: Vec::new(),
21        }
22    }
23}
24
25impl Autocomplete for SlashCommandAutocomplete {
26    fn get_suggestions(&mut self, input: &str) -> Result<Vec<String>, inquire::CustomUserError> {
27        // Only show suggestions when input starts with /
28        if !input.starts_with('/') {
29            self.filtered_commands.clear();
30            return Ok(vec![]);
31        }
32
33        let filter = input.trim_start_matches('/').to_lowercase();
34
35        // Store the command names for use in get_completion
36        self.filtered_commands = SLASH_COMMANDS.iter()
37            .filter(|cmd| {
38                cmd.name.to_lowercase().starts_with(&filter) ||
39                cmd.alias.map(|a| a.to_lowercase().starts_with(&filter)).unwrap_or(false)
40            })
41            .take(6)
42            .map(|cmd| cmd.name)
43            .collect();
44
45        // Return formatted suggestions for display
46        let suggestions: Vec<String> = SLASH_COMMANDS.iter()
47            .filter(|cmd| {
48                cmd.name.to_lowercase().starts_with(&filter) ||
49                cmd.alias.map(|a| a.to_lowercase().starts_with(&filter)).unwrap_or(false)
50            })
51            .take(6)
52            .map(|cmd| format!("/{:<12} {}", cmd.name, cmd.description))
53            .collect();
54
55        Ok(suggestions)
56    }
57
58    fn get_completion(
59        &mut self,
60        _input: &str,
61        highlighted_suggestion: Option<String>,
62    ) -> Result<Replacement, inquire::CustomUserError> {
63        if let Some(suggestion) = highlighted_suggestion {
64            // Extract just the command name - first word after the /
65            // Format is: "/model        Select a different AI model"
66            if let Some(cmd_with_slash) = suggestion.split_whitespace().next() {
67                return Ok(Replacement::Some(cmd_with_slash.to_string()));
68            }
69        }
70        Ok(Replacement::None)
71    }
72}