Skip to main content

lb_rs/search/
mod.rs

1pub mod content;
2pub mod path;
3
4use std::collections::{HashMap, HashSet};
5use std::ops::Range;
6use uuid::Uuid;
7
8use crate::model::file::File;
9
10pub use content::ContentSearcher;
11pub use path::PathSearcher;
12
13/// Unified search result for both path and content searches.
14#[derive(Debug, Clone, Default)]
15pub struct SearchResult {
16    pub id: Uuid,
17    pub filename: String,
18    pub parent_path: String,
19    pub is_folder: bool,
20    /// Character indices in the full path that matched (for path search highlighting).
21    pub path_indices: Vec<u32>,
22    pub path_matches: Vec<ContentMatch>,
23    /// Content matches within the document.
24    pub content_matches: Vec<ContentMatch>,
25}
26
27/// Scopes a search to a subset of the working set.
28#[derive(Debug, Clone)]
29pub enum SearchFilter {
30    /// Restrict results to a folder path.
31    Path(String),
32}
33
34/// A match highlight within document content.
35#[derive(Debug, Clone)]
36pub struct ContentMatch {
37    /// Byte range into the document content.
38    pub range: Range<usize>,
39    /// Whether this is an exact match of the full query.
40    pub exact: bool,
41}
42
43use crate::Lb;
44
45impl Lb {
46    pub async fn path_searcher(&self) -> PathSearcher {
47        PathSearcher::new(self).await
48    }
49}
50
51/// Map each file id to all of its descendant ids (children, grandchildren, ...).
52/// Built once when an executor's index is constructed.
53pub(crate) fn build_descendants(files: &[File]) -> HashMap<Uuid, Vec<Uuid>> {
54    let parent_of: HashMap<Uuid, Uuid> = files.iter().map(|f| (f.id, f.parent)).collect();
55
56    let mut descendants: HashMap<Uuid, Vec<Uuid>> = HashMap::new();
57    for f in files {
58        let mut node = f.id;
59        while let Some(&parent) = parent_of.get(&node) {
60            if parent == node {
61                break; // root is its own parent
62            }
63            descendants.entry(parent).or_default().push(f.id);
64            node = parent;
65        }
66    }
67
68    descendants
69}
70
71/// Resolve a filter to the set of file ids it admits (the scoped folder's
72/// descendants), or `None` when there is no filter.
73pub(crate) fn resolve_filter(
74    filter: Option<SearchFilter>, path_to_id: &HashMap<String, Uuid>,
75    descendants: &HashMap<Uuid, Vec<Uuid>>,
76) -> Option<HashSet<Uuid>> {
77    filter.map(|SearchFilter::Path(path)| {
78        path_to_id
79            .get(&path)
80            .and_then(|id| descendants.get(id))
81            .map(|ids| ids.iter().copied().collect())
82            .unwrap_or_default()
83    })
84}