ass_editor/utils/indexing/
linear.rs1use super::common::{calculate_hash, IndexEntry};
4use crate::core::{EditorDocument, Position, Range, Result};
5use crate::utils::search::{DocumentSearch, SearchOptions, SearchResult, SearchStats};
6
7#[cfg(feature = "std")]
8use std::{borrow::Cow, collections::HashMap, time::Instant};
9
10#[cfg(not(feature = "std"))]
11use alloc::{
12 borrow::Cow,
13 collections::BTreeMap as HashMap,
14 string::{String, ToString},
15 vec::Vec,
16};
17
18#[cfg(not(feature = "std"))]
19type Instant = u64;
20
21pub struct LinearSearchIndex {
23 word_positions: HashMap<String, Vec<IndexEntry>>,
25
26 content_hash: u64,
28
29 build_time: Instant,
31}
32
33impl Default for LinearSearchIndex {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl LinearSearchIndex {
40 pub fn new() -> Self {
42 Self {
43 word_positions: HashMap::new(),
44 content_hash: 0,
45 build_time: {
46 #[cfg(feature = "std")]
47 {
48 Instant::now()
49 }
50 #[cfg(not(feature = "std"))]
51 {
52 0
53 }
54 },
55 }
56 }
57}
58
59impl DocumentSearch for LinearSearchIndex {
60 fn build_index(&mut self, document: &EditorDocument) -> Result<()> {
61 let content = document.text();
62 self.content_hash = calculate_hash(&content);
63 self.build_time = {
64 #[cfg(feature = "std")]
65 {
66 Instant::now()
67 }
68 #[cfg(not(feature = "std"))]
69 {
70 0
71 }
72 };
73
74 self.word_positions.clear();
76
77 let mut line_start = 0;
78
79 for (current_line, line) in content.lines().enumerate() {
80 for (word_start, word) in line.split_whitespace().enumerate() {
81 let entry = IndexEntry {
82 position: Position::new(line_start + word_start),
83 context: line.to_string(),
84 line: current_line,
85 column: word_start,
86 section_type: None,
87 };
88 self.word_positions
89 .entry(word.to_lowercase())
90 .or_default()
91 .push(entry);
92 }
93 line_start += line.len() + 1;
94 }
95
96 Ok(())
97 }
98
99 fn update_index(&mut self, document: &EditorDocument, _changes: &[Range]) -> Result<()> {
100 self.build_index(document)
101 }
102
103 fn search(&self, pattern: &str, _options: &SearchOptions) -> Result<Vec<SearchResult<'_>>> {
104 let query = pattern.to_lowercase();
105 let mut results = Vec::new();
106
107 for (word, entries) in &self.word_positions {
109 if word.contains(&query) {
110 for entry in entries {
111 results.push(SearchResult {
112 start: entry.position,
113 end: Position::new(entry.position.offset + pattern.len()),
114 text: Cow::Owned(pattern.to_string()),
115 context: Cow::Owned(entry.context.clone()),
116 line: entry.line,
117 column: entry.column,
118 });
119 }
120 }
121 }
122
123 Ok(results)
124 }
125
126 fn find_replace<'a>(
127 &'a self,
128 document: &mut EditorDocument,
129 pattern: &str,
130 replacement: &str,
131 options: &SearchOptions,
132 ) -> Result<Vec<SearchResult<'a>>> {
133 let results = self.search(pattern, options)?;
134
135 for result in &results {
136 let range = Range::new(result.start, result.end);
137 document.replace(range, replacement)?;
138 }
139
140 Ok(results)
141 }
142
143 fn stats(&self) -> SearchStats {
144 SearchStats {
145 match_count: self.word_positions.len(),
146 search_time_us: {
147 #[cfg(feature = "std")]
148 {
149 self.build_time.elapsed().as_micros() as u64
150 }
151 #[cfg(not(feature = "std"))]
152 {
153 0
154 }
155 },
156 hit_limit: false,
157 index_size: self.word_positions.len() * 64, }
159 }
160
161 fn clear_index(&mut self) {
162 self.word_positions.clear();
163 }
164}