1use crate::{code_context_menus::CompletionsMenu, editor_settings::SnippetSortOrder};
2use fuzzy::StringMatchCandidate;
3use gpui::TestAppContext;
4use language::CodeLabel;
5use lsp::{CompletionItem, CompletionItemKind, LanguageServerId};
6use project::{Completion, CompletionSource};
7use std::sync::Arc;
8use std::sync::atomic::AtomicBool;
9use text::Anchor;
10
11#[gpui::test]
12async fn test_sort_matches_local_variable_over_global_variable(cx: &mut TestAppContext) {
13 // Case 1: "foo"
14 let completions = vec![
15 CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
16 CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
17 CompletionBuilder::constant("floorf64", "80000000"),
18 CompletionBuilder::constant("floorf32", "80000000"),
19 CompletionBuilder::constant("floorf16", "80000000"),
20 CompletionBuilder::constant("floorf128", "80000000"),
21 ];
22 let matches = sort_matches("foo", &completions, SnippetSortOrder::default(), cx).await;
23 assert_eq!(matches[0], "foo_bar_qux");
24 assert_eq!(matches[1], "foo_bar_baz");
25 assert_eq!(matches[2], "floorf16");
26 assert_eq!(matches[3], "floorf32");
27
28 // Case 2: "foobar"
29 let completions = vec![
30 CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
31 CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
32 ];
33 let matches = sort_matches("foobar", &completions, SnippetSortOrder::default(), cx).await;
34 assert_eq!(matches[0], "foo_bar_qux");
35 assert_eq!(matches[1], "foo_bar_baz");
36}
37
38#[gpui::test]
39async fn test_sort_matches_local_variable_over_global_enum(cx: &mut TestAppContext) {
40 // Case 1: "ele"
41 let completions = vec![
42 CompletionBuilder::constant("ElementType", "7fffffff"),
43 CompletionBuilder::variable("element_type", "7ffffffe"),
44 CompletionBuilder::constant("simd_select", "80000000"),
45 CompletionBuilder::keyword("while let", "7fffffff"),
46 ];
47 let matches = sort_matches("ele", &completions, SnippetSortOrder::default(), cx).await;
48 assert_eq!(matches[0], "element_type");
49 assert_eq!(matches[1], "ElementType");
50
51 // Case 2: "eleme"
52 let completions = vec![
53 CompletionBuilder::constant("ElementType", "7fffffff"),
54 CompletionBuilder::variable("element_type", "7ffffffe"),
55 CompletionBuilder::constant("REPLACEMENT_CHARACTER", "80000000"),
56 ];
57 let matches = sort_matches("eleme", &completions, SnippetSortOrder::default(), cx).await;
58 assert_eq!(matches[0], "element_type");
59 assert_eq!(matches[1], "ElementType");
60}
61
62#[gpui::test]
63async fn test_sort_matches_capitalization(cx: &mut TestAppContext) {
64 // Case 1: "Elem"
65 let completions = vec![
66 CompletionBuilder::constant("ElementType", "7fffffff"),
67 CompletionBuilder::variable("element_type", "7ffffffe"),
68 ];
69 let matches = sort_matches("Elem", &completions, SnippetSortOrder::default(), cx).await;
70 assert_eq!(matches[0], "ElementType");
71 assert_eq!(matches[1], "element_type");
72}
73
74#[gpui::test]
75async fn test_sort_matches_for_unreachable(cx: &mut TestAppContext) {
76 // Case 1: "unre"
77 let completions = vec![
78 CompletionBuilder::function("unreachable", "80000000"),
79 CompletionBuilder::function("unreachable!(…)", "7fffffff"),
80 CompletionBuilder::function("unchecked_rem", "80000000"),
81 CompletionBuilder::function("unreachable_unchecked", "80000000"),
82 ];
83 let matches = sort_matches("unre", &completions, SnippetSortOrder::default(), cx).await;
84 assert_eq!(matches[0], "unreachable!(…)");
85
86 // Case 2: "unrea"
87 let completions = vec![
88 CompletionBuilder::function("unreachable", "80000000"),
89 CompletionBuilder::function("unreachable!(…)", "7fffffff"),
90 CompletionBuilder::function("unreachable_unchecked", "80000000"),
91 ];
92 let matches = sort_matches("unrea", &completions, SnippetSortOrder::default(), cx).await;
93 assert_eq!(matches[0], "unreachable!(…)");
94
95 // Case 3: "unreach"
96 let completions = vec![
97 CompletionBuilder::function("unreachable", "80000000"),
98 CompletionBuilder::function("unreachable!(…)", "7fffffff"),
99 CompletionBuilder::function("unreachable_unchecked", "80000000"),
100 ];
101 let matches = sort_matches("unreach", &completions, SnippetSortOrder::default(), cx).await;
102 assert_eq!(matches[0], "unreachable!(…)");
103
104 // Case 4: "unreachabl"
105 let completions = vec![
106 CompletionBuilder::function("unreachable", "80000000"),
107 CompletionBuilder::function("unreachable!(…)", "7fffffff"),
108 CompletionBuilder::function("unreachable_unchecked", "80000000"),
109 ];
110 let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
111 assert_eq!(matches[0], "unreachable!(…)");
112
113 // Case 5: "unreachable"
114 let completions = vec![
115 CompletionBuilder::function("unreachable", "80000000"),
116 CompletionBuilder::function("unreachable!(…)", "7fffffff"),
117 CompletionBuilder::function("unreachable_unchecked", "80000000"),
118 ];
119 let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
120 assert_eq!(matches[0], "unreachable!(…)");
121}
122
123#[gpui::test]
124async fn test_sort_matches_variable_and_constants_over_function(cx: &mut TestAppContext) {
125 // Case 1: "var" as variable
126 let completions = vec![
127 CompletionBuilder::function("var", "7fffffff"),
128 CompletionBuilder::variable("var", "7fffffff"),
129 ];
130 let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
131 assert_eq!(matches[0], "var");
132 assert_eq!(matches[1], "var");
133
134 // Case 2: "var" as constant
135 let completions = vec![
136 CompletionBuilder::function("var", "7fffffff"),
137 CompletionBuilder::constant("var", "7fffffff"),
138 ];
139 let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
140 assert_eq!(matches[0], "var");
141 assert_eq!(matches[1], "var");
142}
143
144#[gpui::test]
145async fn test_sort_matches_for_jsx_event_handler(cx: &mut TestAppContext) {
146 // Case 1: "on"
147 let completions = vec![
148 CompletionBuilder::function("onCut?", "12"),
149 CompletionBuilder::function("onPlay?", "12"),
150 CompletionBuilder::function("color?", "12"),
151 CompletionBuilder::function("defaultValue?", "12"),
152 CompletionBuilder::function("style?", "12"),
153 CompletionBuilder::function("className?", "12"),
154 ];
155 let matches = sort_matches("on", &completions, SnippetSortOrder::default(), cx).await;
156 assert_eq!(matches[0], "onCut?");
157 assert_eq!(matches[1], "onPlay?");
158
159 // Case 2: "ona"
160 let completions = vec![
161 CompletionBuilder::function("onAbort?", "12"),
162 CompletionBuilder::function("onAuxClick?", "12"),
163 CompletionBuilder::function("onPlay?", "12"),
164 CompletionBuilder::function("onLoad?", "12"),
165 CompletionBuilder::function("onDrag?", "12"),
166 CompletionBuilder::function("onPause?", "12"),
167 CompletionBuilder::function("onPaste?", "12"),
168 CompletionBuilder::function("onAnimationEnd?", "12"),
169 CompletionBuilder::function("onAbortCapture?", "12"),
170 CompletionBuilder::function("onChange?", "12"),
171 CompletionBuilder::function("onWaiting?", "12"),
172 CompletionBuilder::function("onCanPlay?", "12"),
173 ];
174 let matches = sort_matches("ona", &completions, SnippetSortOrder::default(), cx).await;
175 assert_eq!(matches[0], "onAbort?");
176 assert_eq!(matches[1], "onAuxClick?");
177}
178
179#[gpui::test]
180async fn test_sort_matches_for_snippets(cx: &mut TestAppContext) {
181 // Case 1: "prin"
182 let completions = vec![
183 CompletionBuilder::constant("println", "80000000"),
184 CompletionBuilder::snippet("println!(…)", "80000000"),
185 ];
186 let matches = sort_matches("prin", &completions, SnippetSortOrder::Top, cx).await;
187 assert_eq!(matches[0], "println!(…)");
188}
189
190#[gpui::test]
191async fn test_sort_matches_for_exact_match(cx: &mut TestAppContext) {
192 // Case 1: "set_text"
193 let completions = vec![
194 CompletionBuilder::function("set_text", "7fffffff"),
195 CompletionBuilder::function("set_placeholder_text", "7fffffff"),
196 CompletionBuilder::function("set_text_style_refinement", "7fffffff"),
197 CompletionBuilder::function("set_context_menu_options", "7fffffff"),
198 CompletionBuilder::function("select_to_next_word_end", "7fffffff"),
199 CompletionBuilder::function("select_to_next_subword_end", "7fffffff"),
200 CompletionBuilder::function("set_custom_context_menu", "7fffffff"),
201 CompletionBuilder::function("select_to_end_of_excerpt", "7fffffff"),
202 CompletionBuilder::function("select_to_start_of_excerpt", "7fffffff"),
203 CompletionBuilder::function("select_to_start_of_next_excerpt", "7fffffff"),
204 CompletionBuilder::function("select_to_end_of_previous_excerpt", "7fffffff"),
205 ];
206 let matches = sort_matches("set_text", &completions, SnippetSortOrder::Top, cx).await;
207 assert_eq!(matches[0], "set_text");
208 assert_eq!(matches[1], "set_text_style_refinement");
209 assert_eq!(matches[2], "set_context_menu_options");
210}
211
212#[gpui::test]
213async fn test_sort_matches_for_prefix_matches(cx: &mut TestAppContext) {
214 // Case 1: "set"
215 let completions = vec![
216 CompletionBuilder::function("select_to_beginning", "7fffffff"),
217 CompletionBuilder::function("set_collapse_matches", "7fffffff"),
218 CompletionBuilder::function("set_autoindent", "7fffffff"),
219 CompletionBuilder::function("set_all_diagnostics_active", "7fffffff"),
220 CompletionBuilder::function("select_to_end_of_line", "7fffffff"),
221 CompletionBuilder::function("select_all", "7fffffff"),
222 CompletionBuilder::function("select_line", "7fffffff"),
223 CompletionBuilder::function("select_left", "7fffffff"),
224 CompletionBuilder::function("select_down", "7fffffff"),
225 ];
226 let matches = sort_matches("set", &completions, SnippetSortOrder::Top, cx).await;
227 assert_eq!(matches[0], "set_autoindent");
228 assert_eq!(matches[1], "set_collapse_matches");
229 assert_eq!(matches[2], "set_all_diagnostics_active");
230}
231
232#[gpui::test]
233async fn test_sort_matches_for_await(cx: &mut TestAppContext) {
234 // Case 1: "awa"
235 let completions = vec![
236 CompletionBuilder::keyword("await", "7fffffff"),
237 CompletionBuilder::function("await.ne", "80000010"),
238 CompletionBuilder::function("await.eq", "80000010"),
239 CompletionBuilder::function("await.or", "7ffffff8"),
240 CompletionBuilder::function("await.zip", "80000006"),
241 CompletionBuilder::function("await.xor", "7ffffff8"),
242 CompletionBuilder::function("await.and", "80000006"),
243 CompletionBuilder::function("await.map", "80000006"),
244 CompletionBuilder::function("await.take", "7ffffff8"),
245 ];
246 let matches = sort_matches("awa", &completions, SnippetSortOrder::Top, cx).await;
247 assert_eq!(matches[0], "await");
248
249 // Case 2: "await"
250 let completions = vec![
251 CompletionBuilder::keyword("await", "7fffffff"),
252 CompletionBuilder::function("await.ne", "80000010"),
253 CompletionBuilder::function("await.eq", "80000010"),
254 CompletionBuilder::function("await.or", "7ffffff8"),
255 CompletionBuilder::function("await.zip", "80000006"),
256 CompletionBuilder::function("await.xor", "7ffffff8"),
257 CompletionBuilder::function("await.and", "80000006"),
258 CompletionBuilder::function("await.map", "80000006"),
259 CompletionBuilder::function("await.take", "7ffffff8"),
260 ];
261 let matches = sort_matches("await", &completions, SnippetSortOrder::Top, cx).await;
262 assert_eq!(matches[0], "await");
263}
264
265#[gpui::test]
266async fn test_sort_matches_for_python_init(cx: &mut TestAppContext) {
267 // Case 1: "__in"
268 let completions = vec![
269 CompletionBuilder::function("__init__", "05.0003.__init__"),
270 CompletionBuilder::function("__init__", "05.0003"),
271 CompletionBuilder::function("__instancecheck__", "05.0005.__instancecheck__"),
272 CompletionBuilder::function("__init_subclass__", "05.0004.__init_subclass__"),
273 CompletionBuilder::function("__instancecheck__", "05.0005"),
274 CompletionBuilder::function("__init_subclass__", "05.0004"),
275 ];
276 let matches = sort_matches("__in", &completions, SnippetSortOrder::Top, cx).await;
277 assert_eq!(matches[0], "__init__");
278 assert_eq!(matches[1], "__init__");
279
280 // Case 2: "__ini"
281 let completions = vec![
282 CompletionBuilder::function("__init__", "05.0004.__init__"),
283 CompletionBuilder::function("__init__", "05.0004"),
284 CompletionBuilder::function("__init_subclass__", "05.0003.__init_subclass__"),
285 CompletionBuilder::function("__init_subclass__", "05.0003"),
286 ];
287 let matches = sort_matches("__ini", &completions, SnippetSortOrder::Top, cx).await;
288 assert_eq!(matches[0], "__init__");
289 assert_eq!(matches[1], "__init__");
290
291 // Case 3: "__init"
292 let completions = vec![
293 CompletionBuilder::function("__init__", "05.0000.__init__"),
294 CompletionBuilder::function("__init__", "05.0000"),
295 CompletionBuilder::function("__init_subclass__", "05.0001.__init_subclass__"),
296 CompletionBuilder::function("__init_subclass__", "05.0001"),
297 ];
298 let matches = sort_matches("__init", &completions, SnippetSortOrder::Top, cx).await;
299 assert_eq!(matches[0], "__init__");
300 assert_eq!(matches[1], "__init__");
301
302 // Case 4: "__init_"
303 let completions = vec![
304 CompletionBuilder::function("__init__", "11.9999.__init__"),
305 CompletionBuilder::function("__init__", "11.9999"),
306 CompletionBuilder::function("__init_subclass__", "05.0000.__init_subclass__"),
307 CompletionBuilder::function("__init_subclass__", "05.0000"),
308 ];
309 let matches = sort_matches("__init_", &completions, SnippetSortOrder::Top, cx).await;
310 assert_eq!(matches[0], "__init__");
311 assert_eq!(matches[1], "__init__");
312}
313
314#[gpui::test]
315async fn test_sort_matches_for_rust_into(cx: &mut TestAppContext) {
316 // Case 1: "int"
317 let completions = vec![
318 CompletionBuilder::function("into", "80000004"),
319 CompletionBuilder::function("try_into", "80000004"),
320 CompletionBuilder::snippet("println", "80000004"),
321 CompletionBuilder::function("clone_into", "80000004"),
322 CompletionBuilder::function("into_searcher", "80000000"),
323 CompletionBuilder::snippet("eprintln", "80000004"),
324 ];
325 let matches = sort_matches("int", &completions, SnippetSortOrder::default(), cx).await;
326 assert_eq!(matches[0], "into");
327
328 // Case 2: "into"
329 let completions = vec![
330 CompletionBuilder::function("into", "80000004"),
331 CompletionBuilder::function("try_into", "80000004"),
332 CompletionBuilder::function("clone_into", "80000004"),
333 CompletionBuilder::function("into_searcher", "80000000"),
334 CompletionBuilder::function("split_terminator", "7fffffff"),
335 CompletionBuilder::function("rsplit_terminator", "7fffffff"),
336 ];
337 let matches = sort_matches("into", &completions, SnippetSortOrder::default(), cx).await;
338 assert_eq!(matches[0], "into");
339}
340
341#[gpui::test]
342async fn test_sort_matches_for_variable_over_function(cx: &mut TestAppContext) {
343 // Case 1: "serial"
344 let completions = vec![
345 CompletionBuilder::function("serialize", "80000000"),
346 CompletionBuilder::function("serialize", "80000000"),
347 CompletionBuilder::variable("serialization_key", "7ffffffe"),
348 CompletionBuilder::function("serialize_version", "80000000"),
349 CompletionBuilder::function("deserialize", "80000000"),
350 ];
351 let matches = sort_matches("serial", &completions, SnippetSortOrder::default(), cx).await;
352 assert_eq!(matches[0], "serialization_key");
353 assert_eq!(matches[1], "serialize");
354 assert_eq!(matches[2], "serialize");
355 assert_eq!(matches[3], "serialize_version");
356 assert_eq!(matches[4], "deserialize");
357}
358
359#[gpui::test]
360async fn test_sort_matches_for_local_methods_over_library(cx: &mut TestAppContext) {
361 // Case 1: "setis"
362 let completions = vec![
363 CompletionBuilder::variable("setISODay", "16"),
364 CompletionBuilder::variable("setISOWeek", "16"),
365 CompletionBuilder::variable("setISOWeekYear", "16"),
366 CompletionBuilder::function("setISOWeekYear", "16"),
367 CompletionBuilder::variable("setIsRefreshing", "11"),
368 CompletionBuilder::function("setFips", "16"),
369 ];
370 let matches = sort_matches("setis", &completions, SnippetSortOrder::default(), cx).await;
371 assert_eq!(matches[0], "setIsRefreshing");
372 assert_eq!(matches[1], "setISODay");
373 assert_eq!(matches[2], "setISOWeek");
374}
375
376#[gpui::test]
377async fn test_sort_matches_for_prioritize_not_exact_match(cx: &mut TestAppContext) {
378 // Case 1: "item"
379 let completions = vec![
380 CompletionBuilder::function("Item", "16"),
381 CompletionBuilder::variable("Item", "16"),
382 CompletionBuilder::variable("items", "11"),
383 CompletionBuilder::function("ItemText", "16"),
384 ];
385 let matches = sort_matches("item", &completions, SnippetSortOrder::default(), cx).await;
386 assert_eq!(matches[0], "items");
387 assert_eq!(matches[1], "Item");
388 assert_eq!(matches[2], "Item");
389 assert_eq!(matches[3], "ItemText");
390}
391
392#[gpui::test]
393async fn test_sort_matches_for_tailwind_classes(cx: &mut TestAppContext) {
394 let completions = vec![
395 CompletionBuilder::function("rounded-full", "15788"),
396 CompletionBuilder::variable("rounded-t-full", "15846"),
397 CompletionBuilder::variable("rounded-b-full", "15731"),
398 CompletionBuilder::function("rounded-tr-full", "15866"),
399 ];
400 // Case 1: "rounded-full"
401 let matches = sort_matches(
402 "rounded-full",
403 &completions,
404 SnippetSortOrder::default(),
405 cx,
406 )
407 .await;
408 assert_eq!(matches[0], "rounded-full");
409 // Case 2: "roundedfull"
410 let matches = sort_matches("roundedfull", &completions, SnippetSortOrder::default(), cx).await;
411 assert_eq!(matches[0], "rounded-full");
412}
413
414struct CompletionBuilder;
415
416impl CompletionBuilder {
417 fn constant(label: &str, sort_text: &str) -> Completion {
418 Self::new(label, sort_text, CompletionItemKind::CONSTANT)
419 }
420
421 fn function(label: &str, sort_text: &str) -> Completion {
422 Self::new(label, sort_text, CompletionItemKind::FUNCTION)
423 }
424
425 fn variable(label: &str, sort_text: &str) -> Completion {
426 Self::new(label, sort_text, CompletionItemKind::VARIABLE)
427 }
428
429 fn keyword(label: &str, sort_text: &str) -> Completion {
430 Self::new(label, sort_text, CompletionItemKind::KEYWORD)
431 }
432
433 fn snippet(label: &str, sort_text: &str) -> Completion {
434 Self::new(label, sort_text, CompletionItemKind::SNIPPET)
435 }
436
437 fn new(label: &str, sort_text: &str, kind: CompletionItemKind) -> Completion {
438 Completion {
439 replace_range: Anchor::MIN..Anchor::MAX,
440 new_text: label.to_string(),
441 label: CodeLabel {
442 text: label.to_string(),
443 runs: Default::default(),
444 filter_range: 0..label.len(),
445 },
446 documentation: None,
447 source: CompletionSource::Lsp {
448 insert_range: None,
449 server_id: LanguageServerId(0),
450 lsp_completion: Box::new(CompletionItem {
451 label: label.to_string(),
452 kind: Some(kind),
453 sort_text: Some(sort_text.to_string()),
454 ..Default::default()
455 }),
456 lsp_defaults: None,
457 resolved: false,
458 },
459 icon_path: None,
460 insert_text_mode: None,
461 confirm: None,
462 }
463 }
464}
465
466async fn sort_matches(
467 query: &str,
468 completions: &Vec<Completion>,
469 snippet_sort_order: SnippetSortOrder,
470 cx: &mut TestAppContext,
471) -> Vec<String> {
472 let candidates: Arc<[StringMatchCandidate]> = completions
473 .iter()
474 .enumerate()
475 .map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.text))
476 .collect();
477 let cancel_flag = Arc::new(AtomicBool::new(false));
478 let background_executor = cx.executor();
479 let matches = fuzzy::match_strings(
480 &candidates,
481 query,
482 query.chars().any(|c| c.is_uppercase()),
483 100,
484 &cancel_flag,
485 background_executor,
486 )
487 .await;
488 let sorted_matches = CompletionsMenu::sort_string_matches(
489 matches,
490 Some(query),
491 snippet_sort_order,
492 &completions,
493 );
494 sorted_matches.into_iter().map(|m| m.string).collect()
495}