fresh/app/
scan_orchestrators.rs1use rust_i18n::t;
10
11use crate::model::event::BufferId;
12use crate::view::prompt::PromptType;
13
14use super::Editor;
15
16impl Editor {
17 pub fn start_incremental_line_scan(&mut self, open_goto_line: bool) {
26 let buffer_id = self.active_buffer();
27 if let Some(state) = self
28 .windows
29 .get_mut(&self.active_window)
30 .map(|w| &mut w.buffers)
31 .expect("active window present")
32 .get_mut(&buffer_id)
33 {
34 let (chunks, total_bytes) = state.buffer.prepare_line_scan();
35 let leaves = state.buffer.piece_tree_leaves();
36 self.active_window_mut().line_scan.start(
37 buffer_id,
38 leaves,
39 chunks,
40 total_bytes,
41 open_goto_line,
42 );
43 self.set_status_message(t!("goto.scanning_progress", percent = 0).to_string());
44 }
45 }
46
47 pub fn process_line_scan(&mut self) -> bool {
50 let _span = tracing::info_span!("process_line_scan").entered();
51
52 let Some(buffer_id) = self.active_window_mut().line_scan.buffer_id() else {
53 return false;
54 };
55
56 if let Err(e) = self.process_line_scan_batch(buffer_id) {
57 tracing::warn!("Line scan error: {e}");
58 self.finish_line_scan_with_error(e);
59 return true;
60 }
61
62 if self.active_window_mut().line_scan.is_done() {
63 self.finish_line_scan_ok();
64 } else {
65 let pct = self.active_window_mut().line_scan.progress_percent();
66 self.set_status_message(t!("goto.scanning_progress", percent = pct).to_string());
67 }
68 true
69 }
70
71 fn process_line_scan_batch(&mut self, buffer_id: BufferId) -> std::io::Result<()> {
79 let _span = tracing::info_span!("process_line_scan_batch").entered();
80 let concurrency = self.config.editor.read_concurrency.max(1);
81
82 let mut results: Vec<(usize, usize)> = Vec::new();
83 let mut io_work: Vec<(usize, std::path::PathBuf, u64, usize)> = Vec::new();
84 let active_id = self.active_window;
85 let has_state = self
86 .windows
87 .get(&active_id)
88 .expect("active window present")
89 .buffers
90 .contains_key(&buffer_id);
91 if !has_state {
92 return Ok(());
93 }
94
95 'outer: while results.len() + io_work.len() < concurrency {
99 let batch = self
100 .windows
101 .get_mut(&active_id)
102 .expect("active window present")
103 .line_scan
104 .take_next_chunks(concurrency - (results.len() + io_work.len()));
105 if batch.is_empty() {
106 break;
107 }
108
109 for chunk in batch {
110 if chunk.already_known {
111 continue;
112 }
113
114 let leaf = self
115 .windows
116 .get(&active_id)
117 .expect("active window present")
118 .line_scan
119 .leaves()[chunk.leaf_index]
120 .clone();
121 let leaf = &leaf;
122
123 let win = self.windows.get(&active_id).expect("active window present");
124 let Some(state) = win.buffers.get(&buffer_id) else {
125 break 'outer;
126 };
127 match state.buffer.leaf_io_params(leaf) {
128 None => {
129 let count = state.buffer.scan_leaf(leaf)?;
131 results.push((chunk.leaf_index, count));
132 }
133 Some((path, offset, len)) => {
134 io_work.push((chunk.leaf_index, path, offset, len));
136 }
137 }
138 }
139 }
140
141 if !io_work.is_empty() {
143 let fs = match self
144 .windows
145 .get(&active_id)
146 .expect("active window present")
147 .buffers
148 .get(&buffer_id)
149 {
150 Some(s) => s.buffer.filesystem().clone(),
151 None => return Ok(()),
152 };
153
154 let rt = self
155 .tokio_runtime
156 .as_ref()
157 .ok_or_else(|| std::io::Error::other("async runtime not available"))?;
158
159 let io_results: Vec<std::io::Result<(usize, usize)>> = rt.block_on(async {
160 let mut handles = Vec::with_capacity(io_work.len());
161 for (leaf_idx, path, offset, len) in io_work {
162 let fs = fs.clone();
163 handles.push(tokio::task::spawn_blocking(move || {
164 let count = fs.count_line_feeds_in_range(&path, offset, len)?;
165 Ok((leaf_idx, count))
166 }));
167 }
168
169 let mut results = Vec::with_capacity(handles.len());
170 for handle in handles {
171 results.push(handle.await.unwrap());
172 }
173 results
174 });
175
176 for result in io_results {
177 results.push(result?);
178 }
179 }
180
181 for (leaf_idx, count) in results {
182 self.active_window_mut()
183 .line_scan
184 .append_update(leaf_idx, count);
185 }
186
187 Ok(())
188 }
189
190 fn finish_line_scan_ok(&mut self) {
191 let _span = tracing::info_span!("finish_line_scan_ok").entered();
192 let Some(finished) = self.active_window_mut().line_scan.take_finished() else {
193 return;
194 };
195 if let Some(state) = self
196 .windows
197 .get_mut(&self.active_window)
198 .map(|w| &mut w.buffers)
199 .expect("active window present")
200 .get_mut(&finished.buffer_id)
201 {
202 let _span = tracing::info_span!(
203 "rebuild_with_pristine_saved_root",
204 updates = finished.updates.len()
205 )
206 .entered();
207 state
208 .buffer
209 .rebuild_with_pristine_saved_root(&finished.updates);
210 }
211 self.set_status_message(t!("goto.scan_complete").to_string());
212 if finished.open_goto_line {
213 self.open_goto_line_if_active(finished.buffer_id);
214 }
215 }
216
217 fn finish_line_scan_with_error(&mut self, e: std::io::Error) {
218 let Some(finished) = self.active_window_mut().line_scan.take_finished() else {
219 return;
220 };
221 self.set_status_message(t!("goto.scan_failed", error = e.to_string()).to_string());
222 if finished.open_goto_line {
223 self.open_goto_line_if_active(finished.buffer_id);
224 }
225 }
226
227 fn open_goto_line_if_active(&mut self, buffer_id: BufferId) {
228 if self.active_buffer() == buffer_id {
229 self.start_prompt(
230 t!("file.goto_line_prompt").to_string(),
231 PromptType::GotoLine,
232 );
233 }
234 }
235
236 pub fn process_search_scan(&mut self) -> bool {
241 let Some(buffer_id) = self.active_window_mut().search_scan.buffer_id() else {
242 return false;
243 };
244
245 if let Err(e) = self.process_search_scan_batch(buffer_id) {
246 tracing::warn!("Search scan error: {e}");
247 self.active_window_mut().search_scan.abandon();
248 self.set_status_message(format!("Search failed: {e}"));
249 return true;
250 }
251
252 if self.active_window_mut().search_scan.is_done() {
253 self.finish_search_scan();
254 } else {
255 let pct = self.active_window_mut().search_scan.progress_percent();
256 let match_count = self.active_window_mut().search_scan.match_count();
257 self.set_status_message(format!(
258 "Searching... {}% ({} matches so far)",
259 pct, match_count
260 ));
261 }
262 true
263 }
264
265 fn process_search_scan_batch(
268 &mut self,
269 buffer_id: crate::model::event::BufferId,
270 ) -> std::io::Result<()> {
271 let concurrency = self.config.editor.read_concurrency.max(1);
272
273 for _ in 0..concurrency {
274 if self.active_window_mut().search_scan.is_done() {
275 break;
276 }
277
278 let Some(mut chunked) = self.active_window_mut().search_scan.take_chunked() else {
283 return Ok(());
284 };
285 let result = if let Some(state) = self
286 .windows
287 .get_mut(&self.active_window)
288 .map(|w| &mut w.buffers)
289 .expect("active window present")
290 .get_mut(&buffer_id)
291 {
292 state.buffer.search_scan_next_chunk(&mut chunked)
293 } else {
294 Ok(false)
295 };
296 self.active_window_mut()
297 .search_scan
298 .restore_chunked(chunked);
299
300 match result {
301 Ok(false) => break, Ok(true) => {} Err(e) => return Err(e),
304 }
305 }
306
307 Ok(())
308 }
309
310 fn finish_search_scan(&mut self) {
314 let Some(finished) = self.active_window_mut().search_scan.take_finished() else {
315 return;
316 };
317
318 if let Some(state) = self
322 .windows
323 .get_mut(&self.active_window)
324 .map(|w| &mut w.buffers)
325 .expect("active window present")
326 .get_mut(&finished.buffer_id)
327 {
328 state.buffer.refresh_saved_root_if_unmodified();
329 }
330
331 if finished.match_ranges.is_empty() {
332 self.active_window_mut().search_state = None;
333 self.set_status_message(format!("No matches found for '{}'", finished.query));
334 return;
335 }
336
337 self.finalize_search(
338 &finished.query,
339 finished.match_ranges,
340 finished.capped,
341 None,
342 );
343 }
344}