code_completion_tests.rs

  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}