Partition and sort completion items by union membership. When a variable has a union type (`num_candidates > 1`), members present on **all** candidate types (intersection members) are more likely to be type-safe. This function: 1. Partitions items into intersection and branch-only based on `occurrence_count` vs `num_candidates`. 2. Sorts each partition alphabetically by `filter_text` / `label`.
(
items: Vec<CompletionItem>,
occurrence_count: HashMap<String, usize>,
num_candidates: usize,
)
| 734 | /// When `num_candidates <= 1`, returns `items` unchanged (the items |
| 735 | /// already have correct `sort_text` from [`build_completion_items`]). |
| 736 | pub(crate) fn merge_union_completion_items( |
| 737 | items: Vec<CompletionItem>, |
| 738 | occurrence_count: HashMap<String, usize>, |
| 739 | num_candidates: usize, |
| 740 | ) -> Vec<CompletionItem> { |
| 741 | if num_candidates <= 1 { |
| 742 | return items; |
| 743 | } |
| 744 | |
| 745 | let sort_key = |item: &CompletionItem| -> (u8, String) { |
| 746 | ( |
| 747 | kind_sort_tier(item.kind), |
| 748 | item.filter_text |
| 749 | .as_deref() |
| 750 | .unwrap_or(&item.label) |
| 751 | .to_lowercase(), |
| 752 | ) |
| 753 | }; |
| 754 | |
| 755 | let mut intersection: Vec<CompletionItem> = Vec::new(); |
| 756 | let mut branch_only: Vec<CompletionItem> = Vec::new(); |
| 757 | |
| 758 | for item in items { |
| 759 | let count = occurrence_count.get(&item.label).copied().unwrap_or(1); |
| 760 | if count >= num_candidates { |
| 761 | intersection.push(item); |
| 762 | } else { |
| 763 | branch_only.push(item); |
| 764 | } |
| 765 | } |
| 766 | |
| 767 | intersection.sort_by_key(|item| sort_key(item)); |
| 768 | branch_only.sort_by_key(|item| sort_key(item)); |
| 769 | |
| 770 | // Assign sort_text: "0_NNNNN" for intersection, "1_NNNNN" for |
| 771 | // branch-only. |
| 772 | let mut result = Vec::with_capacity(intersection.len() + branch_only.len()); |
| 773 | |
| 774 | for (i, mut item) in intersection.into_iter().enumerate() { |
| 775 | item.sort_text = Some(format!("0_{:05}", i)); |
| 776 | // Update description to show all contributing class names |
| 777 | // (the initial description only has the first candidate). |
| 778 | if let Some(class_names) = class_names_from_data(&item) { |
| 779 | if let Some(ref mut ld) = item.label_details { |
| 780 | ld.description = Some(class_names); |
| 781 | } else { |
| 782 | item.label_details = Some(CompletionItemLabelDetails { |
| 783 | detail: None, |
| 784 | description: Some(class_names), |
| 785 | }); |
| 786 | } |
| 787 | } |
| 788 | result.push(item); |
| 789 | } |
| 790 | |
| 791 | for (i, mut item) in branch_only.into_iter().enumerate() { |
| 792 | item.sort_text = Some(format!("1_{:05}", i)); |
| 793 | // Add label_details showing the originating class(es) so the |