1use super::SearchResult;
2use crate::Lb;
3use crate::model::file::File;
4use nucleo::{
5 Matcher, Nucleo,
6 pattern::{CaseMatching, Normalization},
7};
8use std::sync::Arc;
9
10pub(crate) fn split_path(path: &str) -> (&str, &str) {
12 let path = path.trim_end_matches('/');
14 match path.rfind('/') {
15 Some(idx) if idx > 0 => (&path[..idx], &path[idx + 1..]),
16 Some(_) => ("/", &path[1..]), None => ("", path),
18 }
19}
20
21#[derive(Clone)]
22struct PathEntry {
23 file: File,
24 path: String,
25 filename: String,
26 parent_path: String,
27}
28
29pub struct PathSearcher {
30 nucleo: Nucleo<PathEntry>,
31 results: Vec<SearchResult>,
32 submitted_query: String,
33}
34
35impl PathSearcher {
36 pub async fn new(lb: &Lb) -> Self {
37 let files = lb.list_metadatas().await.unwrap_or_default();
38 let mut paths = lb.list_paths_with_ids(None).await.unwrap_or_default();
39 paths.retain(|(_, path)| path != "/");
40
41 let notify = Arc::new(|| {});
42 let nucleo: Nucleo<PathEntry> = Nucleo::new(nucleo::Config::DEFAULT, notify, None, 1);
43 let injector = nucleo.injector();
44
45 for (id, path) in &paths {
46 if let Some(file) = files.iter().find(|f| f.id == *id) {
47 let (parent_path, filename) = split_path(path);
48 injector.push(
49 PathEntry {
50 file: file.clone(),
51 path: path.clone(),
52 filename: filename.to_string(),
53 parent_path: parent_path.to_string(),
54 },
55 |entry, cols| {
56 cols[0] = entry.path.as_str().into();
57 },
58 );
59 }
60 }
61
62 Self { nucleo, results: Vec::new(), submitted_query: String::new() }
63 }
64
65 pub fn query(&mut self, input: &str) {
67 self.nucleo.pattern.reparse(
68 0,
69 input,
70 CaseMatching::Smart,
71 Normalization::Smart,
72 input.starts_with(&self.submitted_query),
73 );
74 self.submitted_query = input.to_string();
75
76 while self.nucleo.tick(10).running {}
77
78 self.results.clear();
80 let snapshot = self.nucleo.snapshot();
81 let count = snapshot.matched_item_count().min(100);
82 let mut matcher = Matcher::new(nucleo::Config::DEFAULT);
83
84 for i in 0..count {
85 if let Some(item) = snapshot.get_matched_item(i) {
86 let mut indices = Vec::new();
87 self.nucleo.pattern.column_pattern(0).indices(
88 item.matcher_columns[0].slice(..),
89 &mut matcher,
90 &mut indices,
91 );
92
93 self.results.push(SearchResult {
94 id: item.data.file.id,
95 filename: item.data.filename.clone(),
96 parent_path: item.data.parent_path.clone(),
97 path_indices: indices,
98 path_matches: Vec::new(),
99 content_matches: Vec::new(),
100 });
101 }
102 }
103 }
104
105 pub fn results(&self) -> &[SearchResult] {
107 &self.results
108 }
109}