Skip to main content

krait/lsp/
router.rs

1use std::collections::HashSet;
2
3use crate::commands::find::{ReferenceMatch, SymbolMatch};
4
5/// Merge symbol results from multiple LSP servers, sorted by path:line.
6#[must_use]
7pub fn merge_symbol_results(results: Vec<Vec<SymbolMatch>>) -> Vec<SymbolMatch> {
8    let mut merged: Vec<SymbolMatch> = results.into_iter().flatten().collect();
9    merged.sort_by(|a, b| a.path.cmp(&b.path).then(a.line.cmp(&b.line)));
10
11    // Deduplicate by (path, line)
12    let mut seen = HashSet::new();
13    merged.retain(|m| seen.insert((m.path.clone(), m.line)));
14    merged
15}
16
17/// Merge reference results from multiple LSP servers, deduplicate by path:line.
18#[must_use]
19pub fn merge_reference_results(results: Vec<Vec<ReferenceMatch>>) -> Vec<ReferenceMatch> {
20    let mut merged: Vec<ReferenceMatch> = results.into_iter().flatten().collect();
21
22    // Deduplicate by (path, line)
23    let mut seen = HashSet::new();
24    merged.retain(|m| seen.insert((m.path.clone(), m.line)));
25
26    // Sort: definition first, then by file:line
27    merged.sort_by(|a, b| {
28        b.is_definition
29            .cmp(&a.is_definition)
30            .then(a.path.cmp(&b.path))
31            .then(a.line.cmp(&b.line))
32    });
33    merged
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    fn sym(path: &str, line: u32, kind: &str) -> SymbolMatch {
41        SymbolMatch {
42            path: path.to_string(),
43            line,
44            kind: kind.to_string(),
45            preview: String::new(),
46            body: None,
47        }
48    }
49
50    fn refm(path: &str, line: u32, is_def: bool) -> ReferenceMatch {
51        ReferenceMatch {
52            path: path.to_string(),
53            line,
54            preview: String::new(),
55            is_definition: is_def,
56            containing_symbol: None,
57        }
58    }
59
60    #[test]
61    fn merge_symbols_deduplicates() {
62        let r1 = vec![sym("src/a.rs", 10, "function")];
63        let r2 = vec![sym("src/a.rs", 10, "function"), sym("src/b.ts", 5, "class")];
64
65        let merged = merge_symbol_results(vec![r1, r2]);
66        assert_eq!(merged.len(), 2);
67        assert_eq!(merged[0].path, "src/a.rs");
68        assert_eq!(merged[1].path, "src/b.ts");
69    }
70
71    #[test]
72    fn merge_symbols_sorted_by_path_line() {
73        let r1 = vec![sym("src/z.rs", 1, "function")];
74        let r2 = vec![sym("src/a.rs", 1, "function")];
75
76        let merged = merge_symbol_results(vec![r1, r2]);
77        assert_eq!(merged[0].path, "src/a.rs");
78        assert_eq!(merged[1].path, "src/z.rs");
79    }
80
81    #[test]
82    fn merge_refs_deduplicates() {
83        let r1 = vec![refm("src/a.rs", 10, true)];
84        let r2 = vec![refm("src/a.rs", 10, true), refm("src/b.rs", 20, false)];
85
86        let merged = merge_reference_results(vec![r1, r2]);
87        assert_eq!(merged.len(), 2);
88    }
89
90    #[test]
91    fn merge_refs_definition_first() {
92        let r1 = vec![refm("src/b.rs", 20, false)];
93        let r2 = vec![refm("src/a.rs", 10, true)];
94
95        let merged = merge_reference_results(vec![r1, r2]);
96        assert!(merged[0].is_definition);
97        assert_eq!(merged[0].path, "src/a.rs");
98    }
99
100    #[test]
101    fn merge_empty_inputs() {
102        assert!(merge_symbol_results(vec![]).is_empty());
103        assert!(merge_reference_results(vec![]).is_empty());
104    }
105}