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(¤t_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}