1use gpui::SharedString;
8use herogpui_core::{SelectionBehavior, SelectionMode};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum EscapeKeyBehavior {
13 #[default]
15 ClearSelection,
16 None,
18}
19
20pub(crate) fn reports_changes(mode: SelectionMode) -> bool {
22 mode != SelectionMode::None
23}
24
25pub(crate) fn normalize_selection(keys: Vec<SharedString>, multiple: bool) -> Vec<SharedString> {
30 let mut out: Vec<SharedString> = Vec::with_capacity(keys.len());
31 for key in keys {
32 if out.contains(&key) {
33 continue;
34 }
35 out.push(key);
36 if !multiple {
37 break;
38 }
39 }
40 out
41}
42
43pub(crate) fn toggle_key(selection: &mut Vec<SharedString>, key: &SharedString) {
47 if let Some(at) = selection.iter().position(|k| k == key) {
48 selection.remove(at);
49 } else {
50 selection.push(key.clone());
51 }
52}
53
54pub fn next_selection(
59 current: &[SharedString],
60 key: &SharedString,
61 mode: SelectionMode,
62 disallow_empty: bool,
63) -> Vec<SharedString> {
64 let is_selected = current.iter().any(|k| k == key);
65 match mode {
66 SelectionMode::None => current.to_vec(),
67 SelectionMode::Single => {
68 if is_selected && !disallow_empty {
69 Vec::new()
70 } else {
71 vec![key.clone()]
72 }
73 }
74 SelectionMode::Multiple => {
75 if is_selected {
76 if disallow_empty && current.len() == 1 {
77 return current.to_vec();
78 }
79 current.iter().filter(|k| *k != key).cloned().collect()
80 } else {
81 let mut next = current.to_vec();
82 next.push(key.clone());
83 next
84 }
85 }
86 }
87}
88
89pub(crate) fn next_selection_with_behavior(
98 current: &[SharedString],
99 key: &SharedString,
100 mode: SelectionMode,
101 behavior: SelectionBehavior,
102 disallow_empty: bool,
103) -> Vec<SharedString> {
104 match (mode, behavior) {
105 (SelectionMode::Multiple, SelectionBehavior::Replace) => vec![key.clone()],
106 _ => next_selection(current, key, mode, disallow_empty),
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn keys(items: &[&str]) -> Vec<SharedString> {
115 items
116 .iter()
117 .map(|s| SharedString::from(s.to_string()))
118 .collect()
119 }
120
121 #[test]
122 fn normalize_keeps_first_insertion_order_and_collapses_duplicates() {
123 assert_eq!(
124 normalize_selection(keys(&["b", "a", "b", "c"]), true),
125 keys(&["b", "a", "c"]),
126 "duplicates collapse to their first insertion; the rest keep \
127 insertion order"
128 );
129 assert_eq!(
130 normalize_selection(keys(&["a", "b"]), false),
131 keys(&["a"]),
132 "single mode keeps only the first listed key"
133 );
134 }
135
136 #[test]
137 fn toggle_removes_in_place_and_appends() {
138 let mut selection = keys(&["a", "b", "c"]);
139 toggle_key(&mut selection, &SharedString::from("b"));
140 assert_eq!(selection, keys(&["a", "c"]));
141 toggle_key(&mut selection, &SharedString::from("d"));
142 assert_eq!(selection, keys(&["a", "c", "d"]));
143 }
144
145 #[test]
146 fn only_selection_modes_report_changes() {
147 assert!(!reports_changes(SelectionMode::None));
148 assert!(reports_changes(SelectionMode::Single));
149 assert!(reports_changes(SelectionMode::Multiple));
150 }
151
152 #[test]
153 fn single_replaces() {
154 let next = next_selection(
155 &keys(&["a"]),
156 &SharedString::from("b"),
157 SelectionMode::Single,
158 false,
159 );
160 assert_eq!(next, keys(&["b"]));
161 }
162
163 #[test]
164 fn single_clears_on_reselect() {
165 let next = next_selection(
166 &keys(&["a"]),
167 &SharedString::from("a"),
168 SelectionMode::Single,
169 false,
170 );
171 assert!(next.is_empty());
172 }
173
174 #[test]
175 fn single_keeps_when_empty_disallowed() {
176 let next = next_selection(
177 &keys(&["a"]),
178 &SharedString::from("a"),
179 SelectionMode::Single,
180 true,
181 );
182 assert_eq!(next, keys(&["a"]));
183 }
184
185 #[test]
186 fn multiple_toggles() {
187 let added = next_selection(
188 &keys(&["a"]),
189 &SharedString::from("b"),
190 SelectionMode::Multiple,
191 false,
192 );
193 assert_eq!(added, keys(&["a", "b"]));
194 let removed = next_selection(
195 &keys(&["a", "b"]),
196 &SharedString::from("a"),
197 SelectionMode::Multiple,
198 false,
199 );
200 assert_eq!(removed, keys(&["b"]));
201 }
202
203 #[test]
204 fn multiple_keeps_last_when_empty_disallowed() {
205 let next = next_selection(
206 &keys(&["a"]),
207 &SharedString::from("a"),
208 SelectionMode::Multiple,
209 true,
210 );
211 assert_eq!(next, keys(&["a"]));
212 let next = next_selection(
213 &keys(&["a", "b"]),
214 &SharedString::from("a"),
215 SelectionMode::Multiple,
216 true,
217 );
218 assert_eq!(next, keys(&["b"]));
219 }
220
221 #[test]
222 fn none_is_inert() {
223 let next = next_selection(
224 &keys(&["a"]),
225 &SharedString::from("b"),
226 SelectionMode::None,
227 false,
228 );
229 assert_eq!(next, keys(&["a"]));
230 }
231
232 #[test]
233 fn replace_behavior_collapses_a_multiple_selection() {
234 let next = next_selection_with_behavior(
235 &keys(&["a", "b"]),
236 &SharedString::from("c"),
237 SelectionMode::Multiple,
238 SelectionBehavior::Replace,
239 false,
240 );
241 assert_eq!(next, keys(&["c"]));
242 }
243
244 #[test]
245 fn replace_behavior_keeps_the_activated_key_selected() {
246 let next = next_selection_with_behavior(
247 &keys(&["a", "b"]),
248 &SharedString::from("b"),
249 SelectionMode::Multiple,
250 SelectionBehavior::Replace,
251 false,
252 );
253 assert_eq!(next, keys(&["b"]));
254 }
255}