ass_editor/utils/indexing/
fst_search.rs1use super::common::calculate_hash;
4use super::fst::FstSearchIndex;
5use crate::core::{EditorDocument, Position, Range, Result};
6use crate::utils::search::{DocumentSearch, SearchOptions, SearchResult, SearchStats};
7
8use fst::{automaton, IntoStreamer, Streamer};
9
10use std::time::Instant;
11
12#[cfg(feature = "search-index")]
13impl DocumentSearch for FstSearchIndex {
14 fn build_index(&mut self, document: &EditorDocument) -> Result<()> {
15 let content = document.text();
16 self.content_hash = calculate_hash(&content);
17 self.build_time = {
18 #[cfg(feature = "std")]
19 {
20 Instant::now()
21 }
22 #[cfg(not(feature = "std"))]
23 {
24 0
25 }
26 };
27
28 let words = self.extract_words(&content);
29 self.build_fst(words)?;
30
31 Ok(())
32 }
33
34 fn update_index(&mut self, document: &EditorDocument, _changes: &[Range]) -> Result<()> {
35 self.build_index(document)
38 }
39
40 fn search<'a>(
41 &'a self,
42 pattern: &str,
43 options: &SearchOptions,
44 ) -> Result<Vec<SearchResult<'a>>> {
45 #[cfg(feature = "std")]
46 let _start_time = Instant::now();
47 let mut results = Vec::new();
48
49 if let Some(ref fst_set) = self.fst_set {
50 let query = if options.case_sensitive {
51 pattern.to_string()
52 } else {
53 pattern.to_lowercase()
54 };
55
56 let automaton = automaton::Subsequence::new(&query);
59 let mut stream = fst_set.search(automaton).into_stream();
60 let mut count = 0;
61
62 while let Some(key) = stream.next() {
63 if options.max_results > 0 && count >= options.max_results {
64 break;
65 }
66
67 let key_str = String::from_utf8_lossy(key);
68 if let Some(entries) = self.position_map.get(key_str.as_ref()) {
69 for entry in entries {
70 if self.matches_scope(entry, &options.scope) {
72 results.push(SearchResult {
73 start: entry.position,
74 end: Position::new(entry.position.offset + pattern.len()),
75 text: std::borrow::Cow::Owned(pattern.to_string()),
76 context: std::borrow::Cow::Owned(entry.context.clone()),
77 line: entry.line,
78 column: entry.column,
79 });
80 count += 1;
81
82 if options.max_results > 0 && count >= options.max_results {
83 break;
84 }
85 }
86 }
87 }
88 }
89 }
90
91 Ok(results)
92 }
93
94 fn find_replace<'a>(
95 &'a self,
96 document: &mut EditorDocument,
97 pattern: &str,
98 replacement: &str,
99 options: &SearchOptions,
100 ) -> Result<Vec<SearchResult<'a>>> {
101 let results = self.search(pattern, options)?;
102
103 let mut sorted_results = results.clone();
105 sorted_results.sort_by_key(|r| core::cmp::Reverse(r.start.offset));
106
107 for result in &sorted_results {
108 let range = Range::new(result.start, result.end);
109 document.replace(range, replacement)?;
110 }
111
112 Ok(results)
113 }
114
115 fn stats(&self) -> SearchStats {
116 SearchStats {
117 match_count: self.position_map.len(),
118 search_time_us: {
119 #[cfg(feature = "std")]
120 {
121 self.build_time.elapsed().as_micros() as u64
122 }
123 #[cfg(not(feature = "std"))]
124 {
125 0
126 }
127 },
128 hit_limit: false,
129 index_size: self.index_size,
130 }
131 }
132
133 fn clear_index(&mut self) {
134 self.fst_set = None;
135 self.position_map.clear();
136 self.index_size = 0;
137 }
138}