|
1 |
| -use crate::item::CompletionItem; |
| 1 | +use crate::{ |
| 2 | + CompletionItemKind, CompletionText, |
| 3 | + context::CompletionContext, |
| 4 | + item::CompletionItem, |
| 5 | + relevance::{filtering::CompletionFilter, scoring::CompletionScore}, |
| 6 | +}; |
2 | 7 |
|
3 |
| -pub(crate) struct CompletionBuilder { |
4 |
| - items: Vec<CompletionItem>, |
| 8 | +pub(crate) struct PossibleCompletionItem<'a> { |
| 9 | + pub label: String, |
| 10 | + pub description: String, |
| 11 | + pub kind: CompletionItemKind, |
| 12 | + pub score: CompletionScore<'a>, |
| 13 | + pub filter: CompletionFilter<'a>, |
| 14 | + pub completion_text: Option<CompletionText>, |
5 | 15 | }
|
6 | 16 |
|
7 |
| -impl CompletionBuilder { |
8 |
| - pub fn new() -> Self { |
9 |
| - CompletionBuilder { items: vec![] } |
| 17 | +pub(crate) struct CompletionBuilder<'a> { |
| 18 | + items: Vec<PossibleCompletionItem<'a>>, |
| 19 | + ctx: &'a CompletionContext<'a>, |
| 20 | +} |
| 21 | + |
| 22 | +impl<'a> CompletionBuilder<'a> { |
| 23 | + pub fn new(ctx: &'a CompletionContext) -> Self { |
| 24 | + CompletionBuilder { items: vec![], ctx } |
10 | 25 | }
|
11 | 26 |
|
12 |
| - pub fn add_item(&mut self, item: CompletionItem) { |
| 27 | + pub fn add_item(&mut self, item: PossibleCompletionItem<'a>) { |
13 | 28 | self.items.push(item);
|
14 | 29 | }
|
15 | 30 |
|
16 |
| - pub fn finish(mut self) -> Vec<CompletionItem> { |
17 |
| - self.items |
18 |
| - .sort_by(|a, b| b.score.cmp(&a.score).then_with(|| a.label.cmp(&b.label))); |
| 31 | + pub fn finish(self) -> Vec<CompletionItem> { |
| 32 | + let mut items: Vec<PossibleCompletionItem> = self |
| 33 | + .items |
| 34 | + .into_iter() |
| 35 | + .filter(|i| i.filter.is_relevant(self.ctx).is_some()) |
| 36 | + .collect(); |
| 37 | + |
| 38 | + for item in items.iter_mut() { |
| 39 | + item.score.calc_score(self.ctx); |
| 40 | + } |
| 41 | + |
| 42 | + items.sort_by(|a, b| { |
| 43 | + b.score |
| 44 | + .get_score() |
| 45 | + .cmp(&a.score.get_score()) |
| 46 | + .then_with(|| a.label.cmp(&b.label)) |
| 47 | + }); |
19 | 48 |
|
20 |
| - self.items.dedup_by(|a, b| a.label == b.label); |
21 |
| - self.items.truncate(crate::LIMIT); |
| 49 | + items.dedup_by(|a, b| a.label == b.label); |
| 50 | + items.truncate(crate::LIMIT); |
22 | 51 |
|
23 |
| - let should_preselect_first_item = self.should_preselect_first_item(); |
| 52 | + let should_preselect_first_item = should_preselect_first_item(&items); |
24 | 53 |
|
25 |
| - self.items |
| 54 | + /* |
| 55 | + * LSP Clients themselves sort the completion items. |
| 56 | + * They'll use the `sort_text` property if present (or fallback to the `label`). |
| 57 | + * Since our items are already sorted, we're 'hijacking' the sort_text. |
| 58 | + * We're simply adding the index of the item, padded by zeroes to the max length. |
| 59 | + */ |
| 60 | + let max_padding = items.len().to_string().len(); |
| 61 | + |
| 62 | + items |
26 | 63 | .into_iter()
|
27 | 64 | .enumerate()
|
28 |
| - .map(|(idx, mut item)| { |
29 |
| - if idx == 0 { |
30 |
| - item.preselected = should_preselect_first_item; |
| 65 | + .map(|(idx, item)| { |
| 66 | + let preselected = idx == 0 && should_preselect_first_item; |
| 67 | + |
| 68 | + CompletionItem { |
| 69 | + description: item.description, |
| 70 | + kind: item.kind, |
| 71 | + label: item.label, |
| 72 | + preselected, |
| 73 | + |
| 74 | + // wonderous Rust syntax ftw |
| 75 | + sort_text: format!("{:0>padding$}", idx, padding = max_padding), |
| 76 | + completion_text: item.completion_text, |
31 | 77 | }
|
32 |
| - item |
33 | 78 | })
|
34 | 79 | .collect()
|
35 | 80 | }
|
| 81 | +} |
36 | 82 |
|
37 |
| - fn should_preselect_first_item(&mut self) -> bool { |
38 |
| - let mut items_iter = self.items.iter(); |
39 |
| - let first = items_iter.next(); |
40 |
| - let second = items_iter.next(); |
| 83 | +fn should_preselect_first_item(items: &Vec<PossibleCompletionItem>) -> bool { |
| 84 | + let mut items_iter = items.iter(); |
| 85 | + let first = items_iter.next(); |
| 86 | + let second = items_iter.next(); |
41 | 87 |
|
42 |
| - first.is_some_and(|f| match second { |
43 |
| - Some(s) => (f.score - s.score) > 10, |
44 |
| - None => true, |
45 |
| - }) |
46 |
| - } |
| 88 | + first.is_some_and(|f| match second { |
| 89 | + Some(s) => (f.score.get_score() - s.score.get_score()) > 15, |
| 90 | + None => true, |
| 91 | + }) && items.len() >= 10 |
47 | 92 | }
|
0 commit comments