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    // 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}