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