bubbletea_widgets/textinput/
suggestions.rs

1//! Suggestion system methods for the textinput component.
2
3use super::model::Model;
4
5impl Model {
6    /// Check if a suggestion can be accepted
7    pub(super) fn can_accept_suggestion(&self) -> bool {
8        !self.matched_suggestions.is_empty()
9    }
10
11    /// Update the list of matched suggestions based on current input
12    pub(super) fn update_suggestions(&mut self) {
13        if self.value.is_empty() || self.suggestions.is_empty() {
14            self.matched_suggestions.clear();
15            return;
16        }
17
18        let current_text: String = self.value.iter().collect();
19        let current_lower = current_text.to_lowercase();
20
21        let matches: Vec<Vec<char>> = self
22            .suggestions
23            .iter()
24            .filter(|suggestion| {
25                let suggestion_str: String = suggestion.iter().collect();
26                suggestion_str.to_lowercase().starts_with(&current_lower)
27            })
28            .cloned()
29            .collect();
30
31        if matches != self.matched_suggestions {
32            self.current_suggestion_index = 0;
33        }
34
35        self.matched_suggestions = matches;
36    }
37
38    /// Move to the next suggestion
39    pub(super) fn next_suggestion(&mut self) {
40        if !self.matched_suggestions.is_empty() {
41            self.current_suggestion_index =
42                (self.current_suggestion_index + 1) % self.matched_suggestions.len();
43        }
44    }
45
46    /// Move to the previous suggestion
47    pub(super) fn previous_suggestion(&mut self) {
48        if !self.matched_suggestions.is_empty() {
49            if self.current_suggestion_index == 0 {
50                self.current_suggestion_index = self.matched_suggestions.len() - 1;
51            } else {
52                self.current_suggestion_index -= 1;
53            }
54        }
55    }
56
57    /// Returns the complete list of available suggestions.
58    ///
59    /// This returns all suggestions that were set via `set_suggestions()`,
60    /// regardless of the current input or filtering.
61    ///
62    /// # Returns
63    ///
64    /// A vector of all available suggestion strings
65    ///
66    /// # Examples
67    ///
68    /// ```rust
69    /// use bubbletea_widgets::textinput::new;
70    ///
71    /// let mut input = new();
72    /// input.set_suggestions(vec!["apple".to_string(), "banana".to_string()]);
73    /// let suggestions = input.available_suggestions();
74    /// assert_eq!(suggestions.len(), 2);
75    /// ```
76    ///
77    /// # Note
78    ///
79    /// This method matches Go's AvailableSuggestions method exactly.
80    pub fn available_suggestions(&self) -> Vec<String> {
81        self.suggestions
82            .iter()
83            .map(|s| s.iter().collect())
84            .collect()
85    }
86
87    /// Returns the list of suggestions that match the current input.
88    ///
89    /// This returns only the suggestions that start with the current input value
90    /// (case-insensitive matching). The list is updated automatically as the user types.
91    ///
92    /// # Returns
93    ///
94    /// A vector of suggestion strings that match the current input
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use bubbletea_widgets::textinput::new;
100    ///
101    /// let mut input = new();
102    /// input.set_suggestions(vec![
103    ///     "apple".to_string(),
104    ///     "application".to_string(),
105    ///     "banana".to_string(),
106    /// ]);
107    /// input.set_value("app");
108    /// let matched = input.matched_suggestions();
109    /// assert_eq!(matched.len(), 2); // "apple" and "application"
110    /// ```
111    ///
112    /// # Note
113    ///
114    /// This method matches Go's MatchedSuggestions method exactly.
115    pub fn matched_suggestions(&self) -> Vec<String> {
116        self.matched_suggestions
117            .iter()
118            .map(|s| s.iter().collect())
119            .collect()
120    }
121
122    /// Returns the index of the currently selected suggestion.
123    ///
124    /// This index refers to the position in the matched suggestions list
125    /// (not the complete available suggestions list). Use up/down arrow keys
126    /// or configured navigation bindings to change the selection.
127    ///
128    /// # Returns
129    ///
130    /// The zero-based index of the currently selected suggestion
131    ///
132    /// # Examples
133    ///
134    /// ```rust
135    /// use bubbletea_widgets::textinput::new;
136    ///
137    /// let mut input = new();
138    /// input.set_suggestions(vec!["apple".to_string(), "application".to_string()]);
139    /// input.set_value("app");
140    /// assert_eq!(input.current_suggestion_index(), 0); // First suggestion selected
141    /// ```
142    ///
143    /// # Note
144    ///
145    /// This method matches Go's CurrentSuggestionIndex method exactly.
146    pub fn current_suggestion_index(&self) -> usize {
147        self.current_suggestion_index
148    }
149
150    /// Returns the text of the currently selected suggestion.
151    ///
152    /// If no suggestions are available or the index is out of bounds,
153    /// this returns an empty string.
154    ///
155    /// # Returns
156    ///
157    /// The currently selected suggestion as a `String`, or empty string if none
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use bubbletea_widgets::textinput::new;
163    ///
164    /// let mut input = new();
165    /// input.set_suggestions(vec!["apple".to_string(), "application".to_string()]);
166    /// input.set_value("app");
167    /// assert_eq!(input.current_suggestion(), "apple");
168    /// ```
169    ///
170    /// # Note
171    ///
172    /// This method matches Go's CurrentSuggestion method exactly.
173    pub fn current_suggestion(&self) -> String {
174        if self.current_suggestion_index >= self.matched_suggestions.len() {
175            return String::new();
176        }
177        self.matched_suggestions[self.current_suggestion_index]
178            .iter()
179            .collect()
180    }
181}