Skip to main content

ass_editor/utils/indexing/
linear.rs

1//! Linear search index fallback used when the FST backend is unavailable.
2
3use 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
21/// Linear search fallback for when FST is not available
22pub struct LinearSearchIndex {
23    /// Simple word -> positions mapping
24    word_positions: HashMap<String, Vec<IndexEntry>>,
25
26    /// Content hash for invalidation
27    content_hash: u64,
28
29    /// Build timestamp
30    build_time: Instant,
31}
32
33impl Default for LinearSearchIndex {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl LinearSearchIndex {
40    /// Create a new linear search index
41    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        // Simple word extraction for linear search
75        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        // Simple linear search through indexed words
108        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, // Rough estimate
158        }
159    }
160
161    fn clear_index(&mut self) {
162        self.word_positions.clear();
163    }
164}