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