greppy/search/
results.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct SearchResult {
5    pub path: String,
6    pub content: String,
7    #[serde(skip_serializing_if = "Option::is_none")]
8    pub symbol_name: Option<String>,
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub symbol_type: Option<String>,
11    pub start_line: usize,
12    pub end_line: usize,
13    pub language: String,
14    pub score: f32,
15}
16
17impl SearchResult {
18    /// Check if this result overlaps with another (same file, overlapping lines)
19    fn overlaps(&self, other: &SearchResult) -> bool {
20        self.path == other.path
21            && self.start_line <= other.end_line
22            && other.start_line <= self.end_line
23    }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SearchResponse {
28    pub results: Vec<SearchResult>,
29    pub query: String,
30    pub elapsed_ms: f64,
31    pub project: String,
32}
33
34impl SearchResponse {
35    /// Remove duplicate/overlapping results, keeping highest scoring
36    ///
37    /// Results from overlapping chunks (same file, overlapping line ranges)
38    /// are deduplicated, keeping only the highest scoring one.
39    pub fn deduplicate(&mut self) {
40        if self.results.len() <= 1 {
41            return;
42        }
43
44        // Sort by score descending so we keep highest scoring when deduping
45        self.results.sort_by(|a, b| {
46            b.score
47                .partial_cmp(&a.score)
48                .unwrap_or(std::cmp::Ordering::Equal)
49        });
50
51        let mut deduped = Vec::with_capacity(self.results.len());
52
53        for result in std::mem::take(&mut self.results) {
54            // Check if this overlaps with any already-kept result
55            let dominated = deduped
56                .iter()
57                .any(|kept: &SearchResult| kept.overlaps(&result));
58
59            if !dominated {
60                deduped.push(result);
61            }
62        }
63
64        self.results = deduped;
65    }
66}