1use anyhow::Result as AnyhowResult;
12use rust_i18n::t;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15
16use crate::app::types::SearchState;
17use crate::app::warning_domains::WarningDomain;
18use crate::model::event::{BufferId, Event, LeafId};
19use crate::state::EditorState;
20use crate::view::prompt::PromptType;
21use crate::view::split::SplitViewState;
22
23use super::help;
24use super::Editor;
25
26impl Editor {
27 fn preferred_split_for_file(&self) -> LeafId {
31 let active = self.split_manager.active_split();
32 if self.split_manager.get_label(active.into()).is_none() {
33 return active;
34 }
35 self.split_manager.find_unlabeled_leaf().unwrap_or(active)
36 }
37
38 pub fn open_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
43 let active_had_path = self
47 .buffers
48 .get(&self.active_buffer())
49 .and_then(|s| s.buffer.file_path())
50 .is_some();
51
52 let buffer_id = self.open_file_no_focus(path)?;
53
54 let is_new_buffer = self.active_buffer() != buffer_id;
58
59 if is_new_buffer {
60 self.position_history.commit_pending_movement();
62
63 let cursors = self.active_cursors();
65 let position = cursors.primary().position;
66 let anchor = cursors.primary().anchor;
67 self.position_history
68 .record_movement(self.active_buffer(), position, anchor);
69 self.position_history.commit_pending_movement();
70 }
71
72 self.set_active_buffer(buffer_id);
73
74 if !is_new_buffer && !active_had_path {
81 #[cfg(feature = "plugins")]
82 self.update_plugin_state_snapshot();
83
84 self.plugin_manager.run_hook(
85 "buffer_activated",
86 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
87 );
88 }
89
90 let display_name = self
92 .buffer_metadata
93 .get(&buffer_id)
94 .map(|m| m.display_name.clone())
95 .unwrap_or_else(|| path.display().to_string());
96
97 let is_binary = self
99 .buffers
100 .get(&buffer_id)
101 .map(|s| s.buffer.is_binary())
102 .unwrap_or(false);
103
104 if is_binary {
106 self.status_message = Some(t!("buffer.opened_binary", name = display_name).to_string());
107 } else {
108 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
109 }
110
111 Ok(buffer_id)
112 }
113
114 pub fn open_file_no_focus(&mut self, path: &Path) -> anyhow::Result<BufferId> {
121 let base_dir = if self.filesystem.remote_connection_info().is_some() {
124 self.filesystem
125 .home_dir()
126 .unwrap_or_else(|_| self.working_dir.clone())
127 } else {
128 self.working_dir.clone()
129 };
130
131 let resolved_path = if path.is_relative() {
132 base_dir.join(path)
133 } else {
134 path.to_path_buf()
135 };
136
137 let file_exists = self.filesystem.exists(&resolved_path);
140
141 let display_path = resolved_path.clone();
145
146 let canonical_path = if file_exists {
150 self.filesystem
151 .canonicalize(&resolved_path)
152 .unwrap_or_else(|_| resolved_path.clone())
153 } else {
154 if let Some(parent) = resolved_path.parent() {
156 let canonical_parent = if parent.as_os_str().is_empty() {
157 base_dir.clone()
159 } else {
160 self.filesystem
161 .canonicalize(parent)
162 .unwrap_or_else(|_| parent.to_path_buf())
163 };
164 if let Some(filename) = resolved_path.file_name() {
165 canonical_parent.join(filename)
166 } else {
167 resolved_path
168 }
169 } else {
170 resolved_path
171 }
172 };
173 let path = canonical_path.as_path();
174
175 if self.filesystem.is_dir(path).unwrap_or(false) {
179 anyhow::bail!(t!("buffer.cannot_open_directory"));
180 }
181
182 let already_open = self
184 .buffers
185 .iter()
186 .find(|(_, state)| state.buffer.file_path() == Some(path))
187 .map(|(id, _)| *id);
188
189 if let Some(id) = already_open {
190 return Ok(id);
191 }
192
193 let replace_current = {
196 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
197 !current_state.is_composite_buffer
198 && current_state.buffer.is_empty()
199 && !current_state.buffer.is_modified()
200 && current_state.buffer.file_path().is_none()
201 };
202
203 let buffer_id = if replace_current {
204 self.active_buffer()
206 } else {
207 let id = BufferId(self.next_buffer_id);
209 self.next_buffer_id += 1;
210 id
211 };
212
213 tracing::info!(
215 "[SYNTAX DEBUG] open_file_no_focus: path={:?}, extension={:?}, registry_syntaxes={}, user_extensions={:?}",
216 path,
217 path.extension(),
218 self.grammar_registry.available_syntaxes().len(),
219 self.grammar_registry.user_extensions_debug()
220 );
221 let mut state = if file_exists {
222 let buffer = crate::model::buffer::Buffer::load_from_file(
225 &canonical_path,
226 self.config.editor.large_file_threshold_bytes as usize,
227 Arc::clone(&self.filesystem),
228 )?;
229 let detected = crate::primitives::detected_language::DetectedLanguage::from_path(
230 &display_path,
231 &self.grammar_registry,
232 &self.config.languages,
233 );
234 EditorState::from_buffer_with_language(buffer, detected)
235 } else {
236 EditorState::new_with_path(
238 self.config.editor.large_file_threshold_bytes as usize,
239 Arc::clone(&self.filesystem),
240 path.to_path_buf(),
241 )
242 };
243 let is_binary = state.buffer.is_binary();
247 if is_binary {
248 state.editing_disabled = true;
250 tracing::info!("Detected binary file: {}", path.display());
251 }
252
253 let mut whitespace =
257 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
258 state.buffer_settings.auto_close = self.config.editor.auto_close;
259 state.buffer_settings.auto_surround = self.config.editor.auto_surround;
260 if let Some(lang_config) = self.config.languages.get(&state.language) {
261 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
262 state.buffer_settings.use_tabs = lang_config.use_tabs;
263 state.buffer_settings.tab_size =
265 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
266 if state.buffer_settings.auto_close {
268 if let Some(lang_auto_close) = lang_config.auto_close {
269 state.buffer_settings.auto_close = lang_auto_close;
270 }
271 }
272 if state.buffer_settings.auto_surround {
274 if let Some(lang_auto_surround) = lang_config.auto_surround {
275 state.buffer_settings.auto_surround = lang_auto_surround;
276 }
277 }
278 } else {
279 state.buffer_settings.tab_size = self.config.editor.tab_size;
280 }
281 state.buffer_settings.whitespace = whitespace;
282
283 state
285 .margins
286 .configure_for_line_numbers(self.config.editor.line_numbers);
287
288 self.buffers.insert(buffer_id, state);
289 self.event_logs
290 .insert(buffer_id, crate::model::event::EventLog::new());
291
292 let mut metadata =
294 super::types::BufferMetadata::with_file(path.to_path_buf(), &self.working_dir);
295
296 if is_binary {
298 metadata.binary = true;
299 metadata.read_only = true;
300 metadata.disable_lsp(t!("buffer.binary_file").to_string());
301 }
302
303 if file_exists && !metadata.read_only {
305 if let Ok(file_meta) = self.filesystem.metadata(path) {
306 if file_meta.is_readonly {
307 metadata.read_only = true;
308 }
309 }
310 }
311
312 if metadata.read_only {
314 if let Some(state) = self.buffers.get_mut(&buffer_id) {
315 state.editing_disabled = true;
316 }
317 }
318
319 if !is_binary {
321 self.notify_lsp_file_opened(path, buffer_id, &mut metadata);
322 }
323
324 self.buffer_metadata.insert(buffer_id, metadata);
326
327 let target_split = self.preferred_split_for_file();
330 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
331 view_state.add_buffer(buffer_id);
332 let buf_state = view_state.ensure_buffer_state(buffer_id);
334 buf_state.apply_config_defaults(
335 self.config.editor.line_numbers,
336 self.config.editor.line_wrap,
337 self.config.editor.wrap_indent,
338 self.config.editor.rulers.clone(),
339 );
340 }
341
342 self.restore_global_file_state(buffer_id, path, target_split);
345
346 self.emit_event(
348 crate::model::control_event::events::FILE_OPENED.name,
349 serde_json::json!({
350 "path": path.display().to_string(),
351 "buffer_id": buffer_id.0
352 }),
353 );
354
355 self.watch_file(path);
357
358 self.plugin_manager.run_hook(
360 "after_file_open",
361 crate::services::plugins::hooks::HookArgs::AfterFileOpen {
362 buffer_id,
363 path: path.to_path_buf(),
364 },
365 );
366
367 Ok(buffer_id)
368 }
369
370 pub fn open_local_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
376 let resolved_path = if path.is_relative() {
378 self.working_dir.join(path)
379 } else {
380 path.to_path_buf()
381 };
382
383 let display_path = resolved_path.clone();
385
386 let canonical_path = resolved_path
388 .canonicalize()
389 .unwrap_or_else(|_| resolved_path.clone());
390 let path = canonical_path.as_path();
391
392 let already_open = self
394 .buffers
395 .iter()
396 .find(|(_, state)| state.buffer.file_path() == Some(path))
397 .map(|(id, _)| *id);
398
399 if let Some(id) = already_open {
400 self.set_active_buffer(id);
401 return Ok(id);
402 }
403
404 let buffer_id = BufferId(self.next_buffer_id);
406 self.next_buffer_id += 1;
407
408 let buffer = crate::model::buffer::Buffer::load_from_file(
411 &canonical_path,
412 self.config.editor.large_file_threshold_bytes as usize,
413 Arc::clone(&self.local_filesystem),
414 )?;
415 let detected = crate::primitives::detected_language::DetectedLanguage::from_path(
416 &display_path,
417 &self.grammar_registry,
418 &self.config.languages,
419 );
420 let state = EditorState::from_buffer_with_language(buffer, detected);
421
422 self.buffers.insert(buffer_id, state);
423 self.event_logs
424 .insert(buffer_id, crate::model::event::EventLog::new());
425
426 let metadata =
428 super::types::BufferMetadata::with_file(path.to_path_buf(), &self.working_dir);
429 self.buffer_metadata.insert(buffer_id, metadata);
430
431 let target_split = self.preferred_split_for_file();
433 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
434 view_state.add_buffer(buffer_id);
435 let buf_state = view_state.ensure_buffer_state(buffer_id);
436 buf_state.apply_config_defaults(
437 self.config.editor.line_numbers,
438 self.config.editor.line_wrap,
439 self.config.editor.wrap_indent,
440 self.config.editor.rulers.clone(),
441 );
442 }
443
444 self.set_active_buffer(buffer_id);
445
446 let display_name = path.display().to_string();
447 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
448
449 Ok(buffer_id)
450 }
451
452 pub fn open_file_with_encoding(
457 &mut self,
458 path: &Path,
459 encoding: crate::model::buffer::Encoding,
460 ) -> anyhow::Result<BufferId> {
461 let base_dir = self.working_dir.clone();
463
464 let resolved_path = if path.is_relative() {
465 base_dir.join(path)
466 } else {
467 path.to_path_buf()
468 };
469
470 let display_path = resolved_path.clone();
472
473 let canonical_path = self
475 .filesystem
476 .canonicalize(&resolved_path)
477 .unwrap_or_else(|_| resolved_path.clone());
478 let path = canonical_path.as_path();
479
480 let already_open = self
482 .buffers
483 .iter()
484 .find(|(_, state)| state.buffer.file_path() == Some(path))
485 .map(|(id, _)| *id);
486
487 if let Some(id) = already_open {
488 if let Some(state) = self.buffers.get_mut(&id) {
490 state.buffer.set_encoding(encoding);
491 }
492 self.set_active_buffer(id);
493 return Ok(id);
494 }
495
496 let buffer_id = BufferId(self.next_buffer_id);
498 self.next_buffer_id += 1;
499
500 let buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
502 path,
503 encoding,
504 Arc::clone(&self.filesystem),
505 crate::model::buffer::BufferConfig {
506 estimated_line_length: self.config.editor.estimated_line_length,
507 },
508 )?;
509 let detected = crate::primitives::detected_language::DetectedLanguage::from_path(
512 &display_path,
513 &self.grammar_registry,
514 &self.config.languages,
515 );
516
517 let mut state = EditorState::from_buffer_with_language(buffer, detected);
518
519 state
520 .margins
521 .configure_for_line_numbers(self.config.editor.line_numbers);
522
523 self.buffers.insert(buffer_id, state);
524 self.event_logs
525 .insert(buffer_id, crate::model::event::EventLog::new());
526
527 let metadata =
528 super::types::BufferMetadata::with_file(path.to_path_buf(), &self.working_dir);
529 self.buffer_metadata.insert(buffer_id, metadata);
530
531 let target_split = self.preferred_split_for_file();
533 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
534 view_state.add_buffer(buffer_id);
535 let buf_state = view_state.ensure_buffer_state(buffer_id);
536 buf_state.apply_config_defaults(
537 self.config.editor.line_numbers,
538 self.config.editor.line_wrap,
539 self.config.editor.wrap_indent,
540 self.config.editor.rulers.clone(),
541 );
542 }
543
544 self.set_active_buffer(buffer_id);
545
546 Ok(buffer_id)
547 }
548
549 pub fn reload_with_encoding(
553 &mut self,
554 encoding: crate::model::buffer::Encoding,
555 ) -> anyhow::Result<()> {
556 let buffer_id = self.active_buffer();
557
558 let path = self
560 .buffers
561 .get(&buffer_id)
562 .and_then(|s| s.buffer.file_path().map(|p| p.to_path_buf()))
563 .ok_or_else(|| anyhow::anyhow!("Buffer has no file path"))?;
564
565 if let Some(state) = self.buffers.get(&buffer_id) {
567 if state.buffer.is_modified() {
568 anyhow::bail!("Cannot reload: buffer has unsaved modifications");
569 }
570 }
571
572 let new_buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
574 &path,
575 encoding,
576 Arc::clone(&self.filesystem),
577 crate::model::buffer::BufferConfig {
578 estimated_line_length: self.config.editor.estimated_line_length,
579 },
580 )?;
581
582 if let Some(state) = self.buffers.get_mut(&buffer_id) {
584 state.buffer = new_buffer;
585 state.highlighter.invalidate_all();
587 }
588
589 let split_id = self.split_manager.active_split();
591 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
592 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
593 buf_state.cursors = crate::model::cursor::Cursors::new();
594 }
595 }
596
597 Ok(())
598 }
599
600 pub fn open_file_large_encoding_confirmed(&mut self, path: &Path) -> anyhow::Result<BufferId> {
605 let base_dir = self.working_dir.clone();
607
608 let resolved_path = if path.is_relative() {
609 base_dir.join(path)
610 } else {
611 path.to_path_buf()
612 };
613
614 let display_path = resolved_path.clone();
616
617 let canonical_path = self
619 .filesystem
620 .canonicalize(&resolved_path)
621 .unwrap_or_else(|_| resolved_path.clone());
622 let path = canonical_path.as_path();
623
624 let already_open = self
626 .buffers
627 .iter()
628 .find(|(_, state)| state.buffer.file_path() == Some(path))
629 .map(|(id, _)| *id);
630
631 if let Some(id) = already_open {
632 self.set_active_buffer(id);
633 return Ok(id);
634 }
635
636 let buffer_id = BufferId(self.next_buffer_id);
638 self.next_buffer_id += 1;
639
640 let buffer = crate::model::buffer::Buffer::load_large_file_confirmed(
642 path,
643 Arc::clone(&self.filesystem),
644 )?;
645 let detected = crate::primitives::detected_language::DetectedLanguage::from_path(
648 &display_path,
649 &self.grammar_registry,
650 &self.config.languages,
651 );
652
653 let mut state = EditorState::from_buffer_with_language(buffer, detected);
654
655 state
656 .margins
657 .configure_for_line_numbers(self.config.editor.line_numbers);
658
659 self.buffers.insert(buffer_id, state);
660 self.event_logs
661 .insert(buffer_id, crate::model::event::EventLog::new());
662
663 let metadata =
664 super::types::BufferMetadata::with_file(path.to_path_buf(), &self.working_dir);
665 self.buffer_metadata.insert(buffer_id, metadata);
666
667 let target_split = self.preferred_split_for_file();
669 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
670 view_state.add_buffer(buffer_id);
671 let buf_state = view_state.ensure_buffer_state(buffer_id);
672 buf_state.apply_config_defaults(
673 self.config.editor.line_numbers,
674 self.config.editor.line_wrap,
675 self.config.editor.wrap_indent,
676 self.config.editor.rulers.clone(),
677 );
678 }
679
680 self.set_active_buffer(buffer_id);
681
682 let display_name = self
684 .buffer_metadata
685 .get(&buffer_id)
686 .map(|m| m.display_name.clone())
687 .unwrap_or_else(|| path.display().to_string());
688
689 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
690
691 Ok(buffer_id)
692 }
693
694 fn restore_global_file_state(&mut self, buffer_id: BufferId, path: &Path, split_id: LeafId) {
699 use crate::workspace::PersistedFileWorkspace;
700
701 let file_state = match PersistedFileWorkspace::load(path) {
703 Some(state) => state,
704 None => return, };
706
707 let max_pos = match self.buffers.get(&buffer_id) {
709 Some(buffer) => buffer.buffer.len(),
710 None => return,
711 };
712
713 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
715 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
716 let cursor_pos = file_state.cursor.position.min(max_pos);
717 buf_state.cursors.primary_mut().position = cursor_pos;
718 buf_state.cursors.primary_mut().anchor =
719 file_state.cursor.anchor.map(|a| a.min(max_pos));
720 }
721 view_state.viewport.top_byte = file_state.scroll.top_byte;
722 view_state.viewport.left_column = file_state.scroll.left_column;
723 }
724 }
725
726 fn save_file_state_on_close(&self, buffer_id: BufferId) {
728 use crate::workspace::{
729 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
730 };
731
732 let abs_path = match self.buffer_metadata.get(&buffer_id) {
734 Some(metadata) => match metadata.file_path() {
735 Some(path) => path.to_path_buf(),
736 None => return, },
738 None => return,
739 };
740
741 let view_state = self
743 .split_view_states
744 .values()
745 .find(|vs| vs.has_buffer(buffer_id));
746
747 let view_state = match view_state {
748 Some(vs) => vs,
749 None => return, };
751
752 let buf_state = match view_state.keyed_states.get(&buffer_id) {
754 Some(bs) => bs,
755 None => return,
756 };
757
758 let primary_cursor = buf_state.cursors.primary();
760 let file_state = SerializedFileState {
761 cursor: SerializedCursor {
762 position: primary_cursor.position,
763 anchor: primary_cursor.anchor,
764 sticky_column: primary_cursor.sticky_column,
765 },
766 additional_cursors: buf_state
767 .cursors
768 .iter()
769 .skip(1)
770 .map(|(_, cursor)| SerializedCursor {
771 position: cursor.position,
772 anchor: cursor.anchor,
773 sticky_column: cursor.sticky_column,
774 })
775 .collect(),
776 scroll: SerializedScroll {
777 top_byte: buf_state.viewport.top_byte,
778 top_view_line_offset: buf_state.viewport.top_view_line_offset,
779 left_column: buf_state.viewport.left_column,
780 },
781 view_mode: Default::default(),
782 compose_width: None,
783 plugin_state: std::collections::HashMap::new(),
784 folds: Vec::new(),
785 };
786
787 PersistedFileWorkspace::save(&abs_path, file_state);
789 tracing::debug!("Saved file state on close for {:?}", abs_path);
790 }
791
792 pub fn goto_line_col(&mut self, line: usize, column: Option<usize>) {
798 if line == 0 {
799 return; }
801
802 let buffer_id = self.active_buffer();
803
804 let cursors = self.active_cursors();
806 let cursor_id = cursors.primary_id();
807 let old_position = cursors.primary().position;
808 let old_anchor = cursors.primary().anchor;
809 let old_sticky_column = cursors.primary().sticky_column;
810
811 if let Some(state) = self.buffers.get(&buffer_id) {
812 let has_line_index = state.buffer.line_count().is_some();
813 let has_line_scan = state.buffer.has_line_feed_scan();
814 let buffer_len = state.buffer.len();
815
816 let target_line = line.saturating_sub(1);
818 let target_col = column.map(|c| c.saturating_sub(1)).unwrap_or(0);
820
821 let mut known_line: Option<usize> = None;
824
825 let position = if has_line_scan && has_line_index {
826 let max_line = state.buffer.line_count().unwrap_or(1).saturating_sub(1);
828 let actual_line = target_line.min(max_line);
829 known_line = Some(actual_line);
830 if let Some(state) = self.buffers.get_mut(&buffer_id) {
832 state
833 .buffer
834 .resolve_line_byte_offset(actual_line)
835 .map(|offset| (offset + target_col).min(buffer_len))
836 .unwrap_or(0)
837 } else {
838 0
839 }
840 } else {
841 let max_line = state.buffer.line_count().unwrap_or(1).saturating_sub(1);
844 let actual_line = target_line.min(max_line);
845 state.buffer.line_col_to_position(actual_line, target_col)
846 };
847
848 let event = Event::MoveCursor {
849 cursor_id,
850 old_position,
851 new_position: position,
852 old_anchor,
853 new_anchor: None,
854 old_sticky_column,
855 new_sticky_column: target_col,
856 };
857
858 let split_id = self.split_manager.active_split();
859 let state = self.buffers.get_mut(&buffer_id).unwrap();
860 let view_state = self.split_view_states.get_mut(&split_id).unwrap();
861 state.apply(&mut view_state.cursors, &event);
862
863 if let Some(line) = known_line {
866 state.primary_cursor_line_number = crate::model::buffer::LineNumber::Absolute(line);
867 }
868 }
869 }
870
871 pub fn select_range(
875 &mut self,
876 start_line: usize,
877 start_col: Option<usize>,
878 end_line: usize,
879 end_col: Option<usize>,
880 ) {
881 if start_line == 0 || end_line == 0 {
882 return;
883 }
884
885 let buffer_id = self.active_buffer();
886
887 let cursors = self.active_cursors();
888 let cursor_id = cursors.primary_id();
889 let old_position = cursors.primary().position;
890 let old_anchor = cursors.primary().anchor;
891 let old_sticky_column = cursors.primary().sticky_column;
892
893 if let Some(state) = self.buffers.get(&buffer_id) {
894 let buffer_len = state.buffer.len();
895
896 let start_line_0 = start_line.saturating_sub(1);
898 let start_col_0 = start_col.map(|c| c.saturating_sub(1)).unwrap_or(0);
899 let end_line_0 = end_line.saturating_sub(1);
900 let end_col_0 = end_col.map(|c| c.saturating_sub(1)).unwrap_or(0);
901
902 let max_line = state.buffer.line_count().unwrap_or(1).saturating_sub(1);
903
904 let start_pos = state
905 .buffer
906 .line_col_to_position(start_line_0.min(max_line), start_col_0)
907 .min(buffer_len);
908 let end_pos = state
909 .buffer
910 .line_col_to_position(end_line_0.min(max_line), end_col_0)
911 .min(buffer_len);
912
913 let event = Event::MoveCursor {
914 cursor_id,
915 old_position,
916 new_position: end_pos,
917 old_anchor,
918 new_anchor: Some(start_pos),
919 old_sticky_column,
920 new_sticky_column: end_col_0,
921 };
922
923 let split_id = self.split_manager.active_split();
924 let state = self.buffers.get_mut(&buffer_id).unwrap();
925 let view_state = self.split_view_states.get_mut(&split_id).unwrap();
926 state.apply(&mut view_state.cursors, &event);
927 }
928 }
929
930 pub fn goto_byte_offset(&mut self, offset: usize) {
932 let buffer_id = self.active_buffer();
933
934 let cursors = self.active_cursors();
935 let cursor_id = cursors.primary_id();
936 let old_position = cursors.primary().position;
937 let old_anchor = cursors.primary().anchor;
938 let old_sticky_column = cursors.primary().sticky_column;
939
940 if let Some(state) = self.buffers.get(&buffer_id) {
941 let buffer_len = state.buffer.len();
942 let position = offset.min(buffer_len);
943
944 let event = Event::MoveCursor {
945 cursor_id,
946 old_position,
947 new_position: position,
948 old_anchor,
949 new_anchor: None,
950 old_sticky_column,
951 new_sticky_column: 0,
952 };
953
954 let split_id = self.split_manager.active_split();
955 let state = self.buffers.get_mut(&buffer_id).unwrap();
956 let view_state = self.split_view_states.get_mut(&split_id).unwrap();
957 state.apply(&mut view_state.cursors, &event);
958 }
959 }
960
961 pub fn new_buffer(&mut self) -> BufferId {
963 self.position_history.commit_pending_movement();
965
966 let cursors = self.active_cursors();
968 let position = cursors.primary().position;
969 let anchor = cursors.primary().anchor;
970 self.position_history
971 .record_movement(self.active_buffer(), position, anchor);
972 self.position_history.commit_pending_movement();
973
974 let buffer_id = BufferId(self.next_buffer_id);
975 self.next_buffer_id += 1;
976
977 let mut state = EditorState::new(
978 self.terminal_width,
979 self.terminal_height,
980 self.config.editor.large_file_threshold_bytes as usize,
981 Arc::clone(&self.filesystem),
982 );
983 state
985 .margins
986 .configure_for_line_numbers(self.config.editor.line_numbers);
987 state
989 .buffer
990 .set_default_line_ending(self.config.editor.default_line_ending.to_line_ending());
991 self.buffers.insert(buffer_id, state);
992 self.event_logs
993 .insert(buffer_id, crate::model::event::EventLog::new());
994 self.buffer_metadata
995 .insert(buffer_id, crate::app::types::BufferMetadata::new());
996
997 self.set_active_buffer(buffer_id);
998
999 let active_split = self.split_manager.active_split();
1003 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
1004 view_state.apply_config_defaults(
1005 self.config.editor.line_numbers,
1006 self.config.editor.line_wrap,
1007 self.config.editor.wrap_indent,
1008 self.config.editor.rulers.clone(),
1009 );
1010 }
1011
1012 self.status_message = Some(t!("buffer.new").to_string());
1013
1014 buffer_id
1015 }
1016
1017 pub fn open_stdin_buffer(
1027 &mut self,
1028 temp_path: &Path,
1029 thread_handle: Option<std::thread::JoinHandle<anyhow::Result<()>>>,
1030 ) -> AnyhowResult<BufferId> {
1031 self.position_history.commit_pending_movement();
1033
1034 let cursors = self.active_cursors();
1036 let position = cursors.primary().position;
1037 let anchor = cursors.primary().anchor;
1038 self.position_history
1039 .record_movement(self.active_buffer(), position, anchor);
1040 self.position_history.commit_pending_movement();
1041
1042 let replace_current = {
1045 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
1046 !current_state.is_composite_buffer
1047 && current_state.buffer.is_empty()
1048 && !current_state.buffer.is_modified()
1049 && current_state.buffer.file_path().is_none()
1050 };
1051
1052 let buffer_id = if replace_current {
1053 self.active_buffer()
1055 } else {
1056 let id = BufferId(self.next_buffer_id);
1058 self.next_buffer_id += 1;
1059 id
1060 };
1061
1062 let file_size = self.filesystem.metadata(temp_path)?.size as usize;
1064
1065 let mut state = EditorState::from_file_with_languages(
1068 temp_path,
1069 self.terminal_width,
1070 self.terminal_height,
1071 self.config.editor.large_file_threshold_bytes as usize,
1072 &self.grammar_registry,
1073 &self.config.languages,
1074 Arc::clone(&self.filesystem),
1075 )?;
1076
1077 state.buffer.clear_file_path();
1080 state.buffer.clear_modified();
1082
1083 state.buffer_settings.tab_size = self.config.editor.tab_size;
1085 state.buffer_settings.auto_close = self.config.editor.auto_close;
1086 state.buffer_settings.auto_surround = self.config.editor.auto_surround;
1087
1088 state
1090 .margins
1091 .configure_for_line_numbers(self.config.editor.line_numbers);
1092
1093 self.buffers.insert(buffer_id, state);
1094 self.event_logs
1095 .insert(buffer_id, crate::model::event::EventLog::new());
1096
1097 let metadata =
1099 super::types::BufferMetadata::new_unnamed(t!("stdin.display_name").to_string());
1100 self.buffer_metadata.insert(buffer_id, metadata);
1101
1102 let active_split = self.split_manager.active_split();
1104 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
1105 view_state.add_buffer(buffer_id);
1106 let buf_state = view_state.ensure_buffer_state(buffer_id);
1107 buf_state.apply_config_defaults(
1108 self.config.editor.line_numbers,
1109 self.config.editor.line_wrap,
1110 self.config.editor.wrap_indent,
1111 self.config.editor.rulers.clone(),
1112 );
1113 }
1114
1115 self.set_active_buffer(buffer_id);
1116
1117 let complete = thread_handle.is_none();
1120 self.stdin_streaming = Some(super::StdinStreamingState {
1121 temp_path: temp_path.to_path_buf(),
1122 buffer_id,
1123 last_known_size: file_size,
1124 complete,
1125 thread_handle,
1126 });
1127
1128 self.status_message = Some(t!("stdin.streaming").to_string());
1130
1131 Ok(buffer_id)
1132 }
1133
1134 pub fn poll_stdin_streaming(&mut self) -> bool {
1137 let Some(ref mut stream_state) = self.stdin_streaming else {
1138 return false;
1139 };
1140
1141 if stream_state.complete {
1142 return false;
1143 }
1144
1145 let mut changed = false;
1146
1147 let current_size = self
1149 .filesystem
1150 .metadata(&stream_state.temp_path)
1151 .map(|m| m.size as usize)
1152 .unwrap_or(stream_state.last_known_size);
1153
1154 if current_size > stream_state.last_known_size {
1156 if let Some(editor_state) = self.buffers.get_mut(&stream_state.buffer_id) {
1157 editor_state
1158 .buffer
1159 .extend_streaming(&stream_state.temp_path, current_size);
1160 }
1161 stream_state.last_known_size = current_size;
1162
1163 self.status_message =
1165 Some(t!("stdin.streaming_bytes", bytes = current_size).to_string());
1166 changed = true;
1167 }
1168
1169 let thread_finished = stream_state
1171 .thread_handle
1172 .as_ref()
1173 .map(|h| h.is_finished())
1174 .unwrap_or(true);
1175
1176 if thread_finished {
1177 if let Some(handle) = stream_state.thread_handle.take() {
1179 match handle.join() {
1180 Ok(Ok(())) => {
1181 tracing::info!("Stdin streaming completed successfully");
1182 }
1183 Ok(Err(e)) => {
1184 tracing::warn!("Stdin streaming error: {}", e);
1185 self.status_message =
1186 Some(t!("stdin.read_error", error = e.to_string()).to_string());
1187 }
1188 Err(_) => {
1189 tracing::warn!("Stdin streaming thread panicked");
1190 self.status_message = Some(t!("stdin.read_error_panic").to_string());
1191 }
1192 }
1193 }
1194 self.complete_stdin_streaming();
1195 changed = true;
1196 }
1197
1198 changed
1199 }
1200
1201 pub fn complete_stdin_streaming(&mut self) {
1204 if let Some(ref mut stream_state) = self.stdin_streaming {
1205 stream_state.complete = true;
1206
1207 let final_size = self
1209 .filesystem
1210 .metadata(&stream_state.temp_path)
1211 .map(|m| m.size as usize)
1212 .unwrap_or(stream_state.last_known_size);
1213
1214 if final_size > stream_state.last_known_size {
1215 if let Some(editor_state) = self.buffers.get_mut(&stream_state.buffer_id) {
1216 editor_state
1217 .buffer
1218 .extend_streaming(&stream_state.temp_path, final_size);
1219 }
1220 stream_state.last_known_size = final_size;
1221 }
1222
1223 self.status_message =
1224 Some(t!("stdin.read_complete", bytes = stream_state.last_known_size).to_string());
1225 }
1226 }
1227
1228 pub fn is_stdin_streaming(&self) -> bool {
1230 self.stdin_streaming
1231 .as_ref()
1232 .map(|s| !s.complete)
1233 .unwrap_or(false)
1234 }
1235
1236 pub fn create_virtual_buffer(
1246 &mut self,
1247 name: String,
1248 mode: String,
1249 read_only: bool,
1250 ) -> BufferId {
1251 let buffer_id = BufferId(self.next_buffer_id);
1252 self.next_buffer_id += 1;
1253
1254 let mut state = EditorState::new(
1255 self.terminal_width,
1256 self.terminal_height,
1257 self.config.editor.large_file_threshold_bytes as usize,
1258 Arc::clone(&self.filesystem),
1259 );
1260 state.set_language_from_name(&name, &self.grammar_registry);
1264
1265 state
1267 .margins
1268 .configure_for_line_numbers(self.config.editor.line_numbers);
1269
1270 self.buffers.insert(buffer_id, state);
1271 self.event_logs
1272 .insert(buffer_id, crate::model::event::EventLog::new());
1273
1274 let metadata = super::types::BufferMetadata::virtual_buffer(name, mode, read_only);
1276 self.buffer_metadata.insert(buffer_id, metadata);
1277
1278 let active_split = self.split_manager.active_split();
1280 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
1281 view_state.add_buffer(buffer_id);
1282 let buf_state = view_state.ensure_buffer_state(buffer_id);
1283 buf_state.apply_config_defaults(
1284 self.config.editor.line_numbers,
1285 self.config.editor.line_wrap,
1286 self.config.editor.wrap_indent,
1287 self.config.editor.rulers.clone(),
1288 );
1289 } else {
1290 let mut view_state =
1292 SplitViewState::with_buffer(self.terminal_width, self.terminal_height, buffer_id);
1293 view_state.apply_config_defaults(
1294 self.config.editor.line_numbers,
1295 self.config.editor.line_wrap,
1296 self.config.editor.wrap_indent,
1297 self.config.editor.rulers.clone(),
1298 );
1299 self.split_view_states.insert(active_split, view_state);
1300 }
1301
1302 buffer_id
1303 }
1304
1305 pub fn set_virtual_buffer_content(
1311 &mut self,
1312 buffer_id: BufferId,
1313 entries: Vec<crate::primitives::text_property::TextPropertyEntry>,
1314 ) -> Result<(), String> {
1315 let old_cursor_pos = self
1317 .split_view_states
1318 .values()
1319 .find(|vs| vs.has_buffer(buffer_id))
1320 .and_then(|vs| vs.keyed_states.get(&buffer_id))
1321 .map(|bs| bs.cursors.primary().position)
1322 .unwrap_or(0);
1323
1324 let state = self
1325 .buffers
1326 .get_mut(&buffer_id)
1327 .ok_or_else(|| "Buffer not found".to_string())?;
1328
1329 let (text, properties, collected_overlays) =
1331 crate::primitives::text_property::TextPropertyManager::from_entries(entries);
1332
1333 state.overlays.clear(&mut state.marker_list);
1338
1339 let current_len = state.buffer.len();
1340 if current_len > 0 {
1341 state.buffer.delete_bytes(0, current_len);
1342 }
1343 state.buffer.insert(0, &text);
1344
1345 state.buffer.clear_modified();
1347
1348 state.text_properties = properties;
1350
1351 {
1353 use crate::view::overlay::{Overlay, OverlayFace};
1354 use fresh_core::overlay::OverlayNamespace;
1355
1356 let inline_ns = OverlayNamespace::from_string("_inline".to_string());
1357
1358 for co in collected_overlays {
1359 let face = OverlayFace::from_options(&co.options);
1360 let mut overlay = Overlay::with_namespace(
1361 &mut state.marker_list,
1362 co.range,
1363 face,
1364 inline_ns.clone(),
1365 );
1366 overlay.extend_to_line_end = co.options.extend_to_line_end;
1367 if let Some(url) = co.options.url {
1368 overlay.url = Some(url);
1369 }
1370 state.overlays.add(overlay);
1371 }
1372 }
1373
1374 let new_len = state.buffer.len();
1376 let clamped_pos = old_cursor_pos.min(new_len);
1377 let new_cursor_pos = state.buffer.snap_to_char_boundary(clamped_pos);
1379
1380 for view_state in self.split_view_states.values_mut() {
1382 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
1383 buf_state.cursors.primary_mut().position = new_cursor_pos;
1384 buf_state.cursors.primary_mut().anchor = None;
1385 }
1386 }
1387
1388 Ok(())
1389 }
1390
1391 pub fn open_help_manual(&mut self) {
1395 let existing_buffer = self
1397 .buffer_metadata
1398 .iter()
1399 .find(|(_, m)| m.display_name == help::HELP_MANUAL_BUFFER_NAME)
1400 .map(|(id, _)| *id);
1401
1402 if let Some(buffer_id) = existing_buffer {
1403 self.set_active_buffer(buffer_id);
1405 return;
1406 }
1407
1408 let buffer_id = self.create_virtual_buffer(
1410 help::HELP_MANUAL_BUFFER_NAME.to_string(),
1411 "special".to_string(),
1412 true,
1413 );
1414
1415 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1417 state.buffer.insert(0, help::HELP_MANUAL_CONTENT);
1418 state.buffer.clear_modified();
1419 state.editing_disabled = true;
1420
1421 state.margins.configure_for_line_numbers(false);
1423 }
1424
1425 self.set_active_buffer(buffer_id);
1426 }
1427
1428 pub fn open_keyboard_shortcuts(&mut self) {
1433 let existing_buffer = self
1435 .buffer_metadata
1436 .iter()
1437 .find(|(_, m)| m.display_name == help::KEYBOARD_SHORTCUTS_BUFFER_NAME)
1438 .map(|(id, _)| *id);
1439
1440 if let Some(buffer_id) = existing_buffer {
1441 self.set_active_buffer(buffer_id);
1443 return;
1444 }
1445
1446 let bindings = self.keybindings.get_all_bindings();
1448
1449 let mut content = String::from("Keyboard Shortcuts\n");
1451 content.push_str("==================\n\n");
1452 content.push_str("Press 'q' to close this buffer.\n\n");
1453
1454 let mut current_context = String::new();
1456 for (key, action) in &bindings {
1457 let (context, action_name) = if let Some(bracket_end) = action.find("] ") {
1459 let ctx = &action[1..bracket_end];
1460 let name = &action[bracket_end + 2..];
1461 (ctx.to_string(), name.to_string())
1462 } else {
1463 ("Normal".to_string(), action.clone())
1464 };
1465
1466 if context != current_context {
1468 if !current_context.is_empty() {
1469 content.push('\n');
1470 }
1471 content.push_str(&format!("── {} Mode ──\n\n", context));
1472 current_context = context;
1473 }
1474
1475 content.push_str(&format!(" {:20} {}\n", key, action_name));
1477 }
1478
1479 let buffer_id = self.create_virtual_buffer(
1481 help::KEYBOARD_SHORTCUTS_BUFFER_NAME.to_string(),
1482 "special".to_string(),
1483 true,
1484 );
1485
1486 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1488 state.buffer.insert(0, &content);
1489 state.buffer.clear_modified();
1490 state.editing_disabled = true;
1491
1492 state.margins.configure_for_line_numbers(false);
1494 }
1495
1496 self.set_active_buffer(buffer_id);
1497 }
1498
1499 pub fn show_warnings_popup(&mut self) {
1504 if !self.warning_domains.has_any_warnings() {
1505 self.status_message = Some(t!("warnings.none").to_string());
1506 return;
1507 }
1508
1509 self.open_warning_log();
1511 }
1512
1513 pub fn show_lsp_status_popup(&mut self) {
1516 let has_error = self.warning_domains.lsp.level() == crate::app::WarningLevel::Error;
1517
1518 let language = self
1521 .warning_domains
1522 .lsp
1523 .language
1524 .clone()
1525 .unwrap_or_else(|| {
1526 self.buffers
1528 .get(&self.active_buffer())
1529 .map(|s| s.language.clone())
1530 .unwrap_or_else(|| "unknown".to_string())
1531 });
1532
1533 tracing::info!(
1534 "show_lsp_status_popup: language={}, has_error={}, has_warnings={}",
1535 language,
1536 has_error,
1537 self.warning_domains.lsp.has_warnings()
1538 );
1539
1540 self.plugin_manager.run_hook(
1542 "lsp_status_clicked",
1543 crate::services::plugins::hooks::HookArgs::LspStatusClicked {
1544 language: language.clone(),
1545 has_error,
1546 },
1547 );
1548 tracing::info!("show_lsp_status_popup: hook fired");
1549
1550 if !self.warning_domains.lsp.has_warnings() {
1551 if self.lsp_status.is_empty() {
1552 self.status_message = Some(t!("lsp.no_server_active").to_string());
1553 } else {
1554 self.status_message = Some(t!("lsp.status", status = &self.lsp_status).to_string());
1555 }
1556 return;
1557 }
1558
1559 if has_error && self.plugin_manager.has_hook_handlers("lsp_status_clicked") {
1563 tracing::info!(
1564 "show_lsp_status_popup: has_error=true and plugin registered, skipping warning log"
1565 );
1566 return;
1567 }
1568
1569 self.open_warning_log();
1571 }
1572
1573 pub fn show_file_message_popup(&mut self, message: &str) {
1576 use crate::view::popup::{Popup, PopupPosition};
1577 use ratatui::style::Style;
1578
1579 let md = format!("{}\n\n*esc to dismiss*", message);
1581 let content_width = message.lines().map(|l| l.len()).max().unwrap_or(0) as u16;
1583 let hint_width = 16u16; let popup_width = (content_width.max(hint_width) + 4).clamp(20, 60);
1585
1586 let mut popup = Popup::markdown(&md, &self.theme, Some(&self.grammar_registry));
1587 popup.transient = false;
1588 popup.position = PopupPosition::BelowCursor;
1589 popup.width = popup_width;
1590 popup.max_height = 15;
1591 popup.border_style = Style::default().fg(self.theme.popup_border_fg);
1592 popup.background_style = Style::default().bg(self.theme.popup_bg);
1593
1594 let buffer_id = self.active_buffer();
1595 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1596 state.popups.show(popup);
1597 }
1598 }
1599
1600 pub fn get_text_properties_at_cursor(
1602 &self,
1603 ) -> Option<Vec<&crate::primitives::text_property::TextProperty>> {
1604 let state = self.buffers.get(&self.active_buffer())?;
1605 let cursor_pos = self.active_cursors().primary().position;
1606 Some(state.text_properties.get_at(cursor_pos))
1607 }
1608
1609 pub fn close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {
1611 if let Some(state) = self.buffers.get(&id) {
1613 if state.buffer.is_modified() {
1614 return Err(anyhow::anyhow!("Buffer has unsaved changes"));
1615 }
1616 }
1617 self.close_buffer_internal(id)
1618 }
1619
1620 pub fn force_close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {
1623 self.close_buffer_internal(id)
1624 }
1625
1626 fn close_buffer_internal(&mut self, id: BufferId) -> anyhow::Result<()> {
1628 if let Some((wait_id, _)) = self.wait_tracking.remove(&id) {
1630 self.completed_waits.push(wait_id);
1631 }
1632
1633 self.save_file_state_on_close(id);
1635
1636 if let Some(terminal_id) = self.terminal_buffers.remove(&id) {
1638 self.terminal_manager.close(terminal_id);
1640
1641 let backing_file = self.terminal_backing_files.remove(&terminal_id);
1643 if let Some(ref path) = backing_file {
1644 #[allow(clippy::let_underscore_must_use)]
1646 let _ = self.filesystem.remove_file(path);
1647 }
1648 if let Some(log_file) = self.terminal_log_files.remove(&terminal_id) {
1650 if backing_file.as_ref() != Some(&log_file) {
1651 #[allow(clippy::let_underscore_must_use)]
1653 let _ = self.filesystem.remove_file(&log_file);
1654 }
1655 }
1656
1657 self.terminal_mode_resume.remove(&id);
1659
1660 if self.terminal_mode {
1662 self.terminal_mode = false;
1663 self.key_context = crate::input::keybindings::KeyContext::Normal;
1664 }
1665 }
1666
1667 let active_split = self.split_manager.active_split();
1670 let replacement_from_history = self.split_view_states.get(&active_split).and_then(|vs| {
1671 vs.focus_history
1673 .iter()
1674 .rev()
1675 .find(|&&bid| {
1676 bid != id
1677 && self.buffers.contains_key(&bid)
1678 && !self
1679 .buffer_metadata
1680 .get(&bid)
1681 .map(|m| m.hidden_from_tabs)
1682 .unwrap_or(false)
1683 })
1684 .copied()
1685 });
1686
1687 let visible_replacement = replacement_from_history.or_else(|| {
1689 self.buffers
1690 .keys()
1691 .find(|&&bid| {
1692 bid != id
1693 && !self
1694 .buffer_metadata
1695 .get(&bid)
1696 .map(|m| m.hidden_from_tabs)
1697 .unwrap_or(false)
1698 })
1699 .copied()
1700 });
1701
1702 let is_last_visible_buffer = visible_replacement.is_none();
1703 let replacement_buffer = if is_last_visible_buffer {
1704 self.new_buffer()
1705 } else {
1706 visible_replacement.unwrap()
1707 };
1708
1709 if self.active_buffer() == id {
1714 self.set_active_buffer(replacement_buffer);
1715 }
1716
1717 let splits_to_update = self.split_manager.splits_for_buffer(id);
1719 for split_id in splits_to_update {
1720 self.split_manager
1721 .set_split_buffer(split_id, replacement_buffer);
1722 }
1723
1724 self.buffers.remove(&id);
1725 self.event_logs.remove(&id);
1726 self.seen_byte_ranges.remove(&id);
1727 self.buffer_metadata.remove(&id);
1728 if let Some((request_id, _, _)) = self.semantic_tokens_in_flight.remove(&id) {
1729 self.pending_semantic_token_requests.remove(&request_id);
1730 }
1731 if let Some((request_id, _, _, _)) = self.semantic_tokens_range_in_flight.remove(&id) {
1732 self.pending_semantic_token_range_requests
1733 .remove(&request_id);
1734 }
1735 self.semantic_tokens_range_last_request.remove(&id);
1736 self.semantic_tokens_range_applied.remove(&id);
1737 self.semantic_tokens_full_debounce.remove(&id);
1738
1739 self.panel_ids.retain(|_, &mut buf_id| buf_id != id);
1742
1743 for view_state in self.split_view_states.values_mut() {
1745 view_state.remove_buffer(id);
1746 view_state.remove_from_history(id);
1747 }
1748
1749 if is_last_visible_buffer {
1751 self.focus_file_explorer();
1752 }
1753
1754 Ok(())
1755 }
1756
1757 pub fn switch_buffer(&mut self, id: BufferId) {
1759 if self.buffers.contains_key(&id) && id != self.active_buffer() {
1760 self.position_history.commit_pending_movement();
1762
1763 let cursors = self.active_cursors();
1765 let position = cursors.primary().position;
1766 let anchor = cursors.primary().anchor;
1767 self.position_history
1768 .record_movement(self.active_buffer(), position, anchor);
1769 self.position_history.commit_pending_movement();
1770
1771 self.set_active_buffer(id);
1772 }
1773 }
1774
1775 pub fn close_tab(&mut self) {
1779 let buffer_id = self.active_buffer();
1780 let active_split = self.split_manager.active_split();
1781
1782 let buffer_in_other_splits = self
1784 .split_view_states
1785 .iter()
1786 .filter(|(&split_id, view_state)| {
1787 split_id != active_split && view_state.has_buffer(buffer_id)
1788 })
1789 .count();
1790
1791 let current_split_tabs = self
1793 .split_view_states
1794 .get(&active_split)
1795 .map(|vs| vs.open_buffers.clone())
1796 .unwrap_or_default();
1797
1798 let is_last_viewport = buffer_in_other_splits == 0;
1801
1802 if is_last_viewport {
1803 let has_other_splits = self.split_manager.root().count_leaves() > 1;
1806 if current_split_tabs.len() <= 1 && has_other_splits {
1807 if self.active_state().buffer.is_modified() {
1809 let name = self.get_buffer_display_name(buffer_id);
1810 let save_key = t!("prompt.key.save").to_string();
1811 let discard_key = t!("prompt.key.discard").to_string();
1812 let cancel_key = t!("prompt.key.cancel").to_string();
1813 self.start_prompt(
1814 t!(
1815 "prompt.buffer_modified",
1816 name = name,
1817 save_key = save_key,
1818 discard_key = discard_key,
1819 cancel_key = cancel_key
1820 )
1821 .to_string(),
1822 PromptType::ConfirmCloseBuffer { buffer_id },
1823 );
1824 return;
1825 }
1826 if let Err(e) = self.close_buffer(buffer_id) {
1828 tracing::warn!("Failed to close buffer: {}", e);
1829 }
1830 self.close_active_split();
1831 return;
1832 }
1833
1834 if self.active_state().buffer.is_modified() {
1836 let name = self.get_buffer_display_name(buffer_id);
1838 let save_key = t!("prompt.key.save").to_string();
1839 let discard_key = t!("prompt.key.discard").to_string();
1840 let cancel_key = t!("prompt.key.cancel").to_string();
1841 self.start_prompt(
1842 t!(
1843 "prompt.buffer_modified",
1844 name = name,
1845 save_key = save_key,
1846 discard_key = discard_key,
1847 cancel_key = cancel_key
1848 )
1849 .to_string(),
1850 PromptType::ConfirmCloseBuffer { buffer_id },
1851 );
1852 } else if let Err(e) = self.close_buffer(buffer_id) {
1853 self.set_status_message(t!("file.cannot_close", error = e.to_string()).to_string());
1854 } else {
1855 self.set_status_message(t!("buffer.tab_closed").to_string());
1856 }
1857 } else {
1858 if current_split_tabs.len() <= 1 {
1860 if self.terminal_mode && self.is_terminal_buffer(buffer_id) {
1863 self.terminal_mode = false;
1864 self.key_context = crate::input::keybindings::KeyContext::Normal;
1865 }
1866 self.close_active_split();
1867 return;
1868 }
1869
1870 let current_idx = current_split_tabs
1872 .iter()
1873 .position(|&id| id == buffer_id)
1874 .unwrap_or(0);
1875 let replacement_idx = if current_idx > 0 { current_idx - 1 } else { 1 };
1876 let replacement_buffer = current_split_tabs[replacement_idx];
1877
1878 if self.terminal_mode && self.is_terminal_buffer(buffer_id) {
1880 self.terminal_mode = false;
1881 self.key_context = crate::input::keybindings::KeyContext::Normal;
1882 }
1883
1884 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
1886 view_state.remove_buffer(buffer_id);
1887 }
1888
1889 self.split_manager
1891 .set_split_buffer(active_split, replacement_buffer);
1892
1893 self.set_status_message(t!("buffer.tab_closed").to_string());
1894 }
1895 }
1896
1897 pub fn close_tab_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
1901 if self.terminal_mode && self.is_terminal_buffer(buffer_id) {
1903 self.terminal_mode = false;
1904 self.key_context = crate::input::keybindings::KeyContext::Normal;
1905 }
1906
1907 let buffer_in_other_splits = self
1909 .split_view_states
1910 .iter()
1911 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
1912 .count();
1913
1914 let split_tabs = self
1916 .split_view_states
1917 .get(&split_id)
1918 .map(|vs| vs.open_buffers.clone())
1919 .unwrap_or_default();
1920
1921 let is_last_viewport = buffer_in_other_splits == 0;
1922
1923 if is_last_viewport {
1924 if let Some(state) = self.buffers.get(&buffer_id) {
1926 if state.buffer.is_modified() {
1927 let name = self.get_buffer_display_name(buffer_id);
1929 let save_key = t!("prompt.key.save").to_string();
1930 let discard_key = t!("prompt.key.discard").to_string();
1931 let cancel_key = t!("prompt.key.cancel").to_string();
1932 self.start_prompt(
1933 t!(
1934 "prompt.buffer_modified",
1935 name = name,
1936 save_key = save_key,
1937 discard_key = discard_key,
1938 cancel_key = cancel_key
1939 )
1940 .to_string(),
1941 PromptType::ConfirmCloseBuffer { buffer_id },
1942 );
1943 return false;
1944 }
1945 }
1946 if let Err(e) = self.close_buffer(buffer_id) {
1947 self.set_status_message(t!("file.cannot_close", error = e.to_string()).to_string());
1948 } else {
1949 self.set_status_message(t!("buffer.tab_closed").to_string());
1950 }
1951 } else {
1952 if split_tabs.len() <= 1 {
1954 self.handle_close_split(split_id.into());
1956 return true;
1957 }
1958
1959 let current_idx = split_tabs
1961 .iter()
1962 .position(|&id| id == buffer_id)
1963 .unwrap_or(0);
1964 let replacement_idx = if current_idx > 0 { current_idx - 1 } else { 1 };
1965 let replacement_buffer = split_tabs[replacement_idx];
1966
1967 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
1969 view_state.remove_buffer(buffer_id);
1970 }
1971
1972 self.split_manager
1974 .set_split_buffer(split_id, replacement_buffer);
1975
1976 self.set_status_message(t!("buffer.tab_closed").to_string());
1977 }
1978 true
1979 }
1980
1981 pub fn close_other_tabs_in_split(&mut self, keep_buffer_id: BufferId, split_id: LeafId) {
1983 let split_tabs = self
1985 .split_view_states
1986 .get(&split_id)
1987 .map(|vs| vs.open_buffers.clone())
1988 .unwrap_or_default();
1989
1990 let tabs_to_close: Vec<_> = split_tabs
1992 .iter()
1993 .filter(|&&id| id != keep_buffer_id)
1994 .copied()
1995 .collect();
1996
1997 let mut closed = 0;
1998 let mut skipped_modified = 0;
1999 for buffer_id in tabs_to_close {
2000 if self.close_tab_in_split_silent(buffer_id, split_id) {
2001 closed += 1;
2002 } else {
2003 skipped_modified += 1;
2004 }
2005 }
2006
2007 self.split_manager
2009 .set_split_buffer(split_id, keep_buffer_id);
2010
2011 self.set_batch_close_status_message(closed, skipped_modified);
2012 }
2013
2014 pub fn close_tabs_to_right_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
2016 let split_tabs = self
2018 .split_view_states
2019 .get(&split_id)
2020 .map(|vs| vs.open_buffers.clone())
2021 .unwrap_or_default();
2022
2023 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
2025 return;
2026 };
2027
2028 let tabs_to_close: Vec<_> = split_tabs.iter().skip(target_idx + 1).copied().collect();
2030
2031 let mut closed = 0;
2032 let mut skipped_modified = 0;
2033 for buf_id in tabs_to_close {
2034 if self.close_tab_in_split_silent(buf_id, split_id) {
2035 closed += 1;
2036 } else {
2037 skipped_modified += 1;
2038 }
2039 }
2040
2041 self.set_batch_close_status_message(closed, skipped_modified);
2042 }
2043
2044 pub fn close_tabs_to_left_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
2046 let split_tabs = self
2048 .split_view_states
2049 .get(&split_id)
2050 .map(|vs| vs.open_buffers.clone())
2051 .unwrap_or_default();
2052
2053 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
2055 return;
2056 };
2057
2058 let tabs_to_close: Vec<_> = split_tabs.iter().take(target_idx).copied().collect();
2060
2061 let mut closed = 0;
2062 let mut skipped_modified = 0;
2063 for buf_id in tabs_to_close {
2064 if self.close_tab_in_split_silent(buf_id, split_id) {
2065 closed += 1;
2066 } else {
2067 skipped_modified += 1;
2068 }
2069 }
2070
2071 self.set_batch_close_status_message(closed, skipped_modified);
2072 }
2073
2074 pub fn close_all_tabs_in_split(&mut self, split_id: LeafId) {
2076 let split_tabs = self
2078 .split_view_states
2079 .get(&split_id)
2080 .map(|vs| vs.open_buffers.clone())
2081 .unwrap_or_default();
2082
2083 let mut closed = 0;
2084 let mut skipped_modified = 0;
2085
2086 for buffer_id in split_tabs {
2088 if self.close_tab_in_split_silent(buffer_id, split_id) {
2089 closed += 1;
2090 } else {
2091 skipped_modified += 1;
2092 }
2093 }
2094
2095 self.set_batch_close_status_message(closed, skipped_modified);
2096 }
2097
2098 fn set_batch_close_status_message(&mut self, closed: usize, skipped_modified: usize) {
2100 let message = match (closed, skipped_modified) {
2101 (0, 0) => t!("buffer.no_tabs_to_close").to_string(),
2102 (0, n) => t!("buffer.skipped_modified", count = n).to_string(),
2103 (n, 0) => t!("buffer.closed_tabs", count = n).to_string(),
2104 (c, s) => t!("buffer.closed_tabs_skipped", closed = c, skipped = s).to_string(),
2105 };
2106 self.set_status_message(message);
2107 }
2108
2109 fn close_tab_in_split_silent(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
2113 if self.terminal_mode && self.is_terminal_buffer(buffer_id) {
2115 self.terminal_mode = false;
2116 self.key_context = crate::input::keybindings::KeyContext::Normal;
2117 }
2118
2119 let buffer_in_other_splits = self
2121 .split_view_states
2122 .iter()
2123 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
2124 .count();
2125
2126 let split_tabs = self
2128 .split_view_states
2129 .get(&split_id)
2130 .map(|vs| vs.open_buffers.clone())
2131 .unwrap_or_default();
2132
2133 let is_last_viewport = buffer_in_other_splits == 0;
2134
2135 if is_last_viewport {
2136 if let Some(state) = self.buffers.get(&buffer_id) {
2139 if state.buffer.is_modified() {
2140 return false;
2142 }
2143 }
2144 if let Err(e) = self.close_buffer(buffer_id) {
2145 tracing::warn!("Failed to close buffer: {}", e);
2146 }
2147 true
2148 } else {
2149 if split_tabs.len() <= 1 {
2151 self.handle_close_split(split_id.into());
2153 return true;
2154 }
2155
2156 let current_idx = split_tabs
2158 .iter()
2159 .position(|&id| id == buffer_id)
2160 .unwrap_or(0);
2161 let replacement_idx = if current_idx > 0 { current_idx - 1 } else { 1 };
2162 let replacement_buffer = split_tabs.get(replacement_idx).copied();
2163
2164 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
2166 view_state.remove_buffer(buffer_id);
2167 }
2168
2169 if let Some(replacement) = replacement_buffer {
2171 self.split_manager.set_split_buffer(split_id, replacement);
2172 }
2173 true
2174 }
2175 }
2176
2177 fn visible_buffers_for_active_split(&self) -> Vec<BufferId> {
2180 let active_split = self.split_manager.active_split();
2181 if let Some(view_state) = self.split_view_states.get(&active_split) {
2182 view_state
2183 .open_buffers
2184 .iter()
2185 .copied()
2186 .filter(|id| {
2187 !self
2188 .buffer_metadata
2189 .get(id)
2190 .map(|m| m.hidden_from_tabs)
2191 .unwrap_or(false)
2192 })
2193 .collect()
2194 } else {
2195 let mut all_ids: Vec<_> = self
2197 .buffers
2198 .keys()
2199 .copied()
2200 .filter(|id| {
2201 !self
2202 .buffer_metadata
2203 .get(id)
2204 .map(|m| m.hidden_from_tabs)
2205 .unwrap_or(false)
2206 })
2207 .collect();
2208 all_ids.sort_by_key(|id| id.0);
2209 all_ids
2210 }
2211 }
2212
2213 pub fn next_buffer(&mut self) {
2215 let ids = self.visible_buffers_for_active_split();
2216
2217 if ids.is_empty() {
2218 return;
2219 }
2220
2221 if let Some(idx) = ids.iter().position(|&id| id == self.active_buffer()) {
2222 let next_idx = (idx + 1) % ids.len();
2223 if ids[next_idx] != self.active_buffer() {
2224 self.position_history.commit_pending_movement();
2226
2227 let cursors = self.active_cursors();
2229 let position = cursors.primary().position;
2230 let anchor = cursors.primary().anchor;
2231 self.position_history
2232 .record_movement(self.active_buffer(), position, anchor);
2233 self.position_history.commit_pending_movement();
2234
2235 self.set_active_buffer(ids[next_idx]);
2236 }
2237 }
2238 }
2239
2240 pub fn prev_buffer(&mut self) {
2242 let ids = self.visible_buffers_for_active_split();
2243
2244 if ids.is_empty() {
2245 return;
2246 }
2247
2248 if let Some(idx) = ids.iter().position(|&id| id == self.active_buffer()) {
2249 let prev_idx = if idx == 0 { ids.len() - 1 } else { idx - 1 };
2250 if ids[prev_idx] != self.active_buffer() {
2251 self.position_history.commit_pending_movement();
2253
2254 let cursors = self.active_cursors();
2256 let position = cursors.primary().position;
2257 let anchor = cursors.primary().anchor;
2258 self.position_history
2259 .record_movement(self.active_buffer(), position, anchor);
2260 self.position_history.commit_pending_movement();
2261
2262 self.set_active_buffer(ids[prev_idx]);
2263 }
2264 }
2265 }
2266
2267 pub fn navigate_back(&mut self) {
2269 self.in_navigation = true;
2271
2272 self.position_history.commit_pending_movement();
2274
2275 if self.position_history.can_go_back() && !self.position_history.can_go_forward() {
2278 let cursors = self.active_cursors();
2279 let position = cursors.primary().position;
2280 let anchor = cursors.primary().anchor;
2281 self.position_history
2282 .record_movement(self.active_buffer(), position, anchor);
2283 self.position_history.commit_pending_movement();
2284 }
2285
2286 if let Some(entry) = self.position_history.back() {
2288 let target_buffer = entry.buffer_id;
2289 let target_position = entry.position;
2290 let target_anchor = entry.anchor;
2291
2292 if self.buffers.contains_key(&target_buffer) {
2294 self.set_active_buffer(target_buffer);
2295
2296 let cursors = self.active_cursors();
2298 let cursor_id = cursors.primary_id();
2299 let old_position = cursors.primary().position;
2300 let old_anchor = cursors.primary().anchor;
2301 let old_sticky_column = cursors.primary().sticky_column;
2302 let event = Event::MoveCursor {
2303 cursor_id,
2304 old_position,
2305 new_position: target_position,
2306 old_anchor,
2307 new_anchor: target_anchor,
2308 old_sticky_column,
2309 new_sticky_column: 0, };
2311 let split_id = self.split_manager.active_split();
2312 let state = self.buffers.get_mut(&target_buffer).unwrap();
2313 let view_state = self.split_view_states.get_mut(&split_id).unwrap();
2314 state.apply(&mut view_state.cursors, &event);
2315 }
2316 }
2317
2318 self.in_navigation = false;
2320 }
2321
2322 pub fn navigate_forward(&mut self) {
2324 self.in_navigation = true;
2326
2327 if let Some(entry) = self.position_history.forward() {
2328 let target_buffer = entry.buffer_id;
2329 let target_position = entry.position;
2330 let target_anchor = entry.anchor;
2331
2332 if self.buffers.contains_key(&target_buffer) {
2334 self.set_active_buffer(target_buffer);
2335
2336 let cursors = self.active_cursors();
2338 let cursor_id = cursors.primary_id();
2339 let old_position = cursors.primary().position;
2340 let old_anchor = cursors.primary().anchor;
2341 let old_sticky_column = cursors.primary().sticky_column;
2342 let event = Event::MoveCursor {
2343 cursor_id,
2344 old_position,
2345 new_position: target_position,
2346 old_anchor,
2347 new_anchor: target_anchor,
2348 old_sticky_column,
2349 new_sticky_column: 0, };
2351 let split_id = self.split_manager.active_split();
2352 let state = self.buffers.get_mut(&target_buffer).unwrap();
2353 let view_state = self.split_view_states.get_mut(&split_id).unwrap();
2354 state.apply(&mut view_state.cursors, &event);
2355 }
2356 }
2357
2358 self.in_navigation = false;
2360 }
2361
2362 pub fn get_mouse_hover_state(&self) -> Option<(usize, u16, u16)> {
2365 self.mouse_state
2366 .lsp_hover_state
2367 .map(|(pos, _, x, y)| (pos, x, y))
2368 }
2369
2370 pub fn has_transient_popup(&self) -> bool {
2372 self.active_state()
2373 .popups
2374 .top()
2375 .is_some_and(|p| p.transient)
2376 }
2377
2378 pub fn force_check_mouse_hover(&mut self) -> bool {
2381 if let Some((byte_pos, _, screen_x, screen_y)) = self.mouse_state.lsp_hover_state {
2383 if !self.mouse_state.lsp_hover_request_sent {
2384 self.mouse_state.lsp_hover_request_sent = true;
2385 self.mouse_hover_screen_position = Some((screen_x, screen_y));
2386 if let Err(e) = self.request_hover_at_position(byte_pos) {
2387 tracing::debug!("Failed to request hover: {}", e);
2388 return false;
2389 }
2390 return true;
2391 }
2392 }
2393 false
2394 }
2395
2396 pub fn queue_file_open(
2402 &mut self,
2403 path: PathBuf,
2404 line: Option<usize>,
2405 column: Option<usize>,
2406 end_line: Option<usize>,
2407 end_column: Option<usize>,
2408 message: Option<String>,
2409 wait_id: Option<u64>,
2410 ) {
2411 self.pending_file_opens.push(super::PendingFileOpen {
2412 path,
2413 line,
2414 column,
2415 end_line,
2416 end_column,
2417 message,
2418 wait_id,
2419 });
2420 }
2421
2422 pub fn process_pending_file_opens(&mut self) -> bool {
2427 if self.pending_file_opens.is_empty() {
2428 return false;
2429 }
2430
2431 let pending = std::mem::take(&mut self.pending_file_opens);
2433 let mut processed_any = false;
2434
2435 for pending_file in pending {
2436 tracing::info!(
2437 "[SYNTAX DEBUG] Processing pending file open: {:?}",
2438 pending_file.path
2439 );
2440
2441 match self.open_file(&pending_file.path) {
2442 Ok(_) => {
2443 if let (Some(line), Some(end_line)) = (pending_file.line, pending_file.end_line)
2445 {
2446 self.select_range(
2447 line,
2448 pending_file.column,
2449 end_line,
2450 pending_file.end_column,
2451 );
2452 } else if let Some(line) = pending_file.line {
2453 self.goto_line_col(line, pending_file.column);
2454 }
2455 let has_popup = pending_file.message.is_some();
2457 if let Some(ref msg) = pending_file.message {
2458 self.show_file_message_popup(msg);
2459 }
2460 if let Some(wait_id) = pending_file.wait_id {
2462 let buffer_id = self.active_buffer();
2463 self.wait_tracking.insert(buffer_id, (wait_id, has_popup));
2464 }
2465 processed_any = true;
2466 }
2467 Err(e) => {
2468 if let Some(confirmation) =
2471 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
2472 {
2473 self.start_large_file_encoding_confirmation(confirmation);
2474 } else {
2475 self.set_status_message(
2477 t!("file.error_opening", error = e.to_string()).to_string(),
2478 );
2479 }
2480 processed_any = true;
2481 }
2482 }
2483 }
2484
2485 processed_any
2486 }
2487
2488 pub fn take_completed_waits(&mut self) -> Vec<u64> {
2490 std::mem::take(&mut self.completed_waits)
2491 }
2492
2493 pub fn remove_wait_tracking(&mut self, wait_id: u64) {
2495 self.wait_tracking.retain(|_, (wid, _)| *wid != wait_id);
2496 }
2497
2498 pub fn start_incremental_line_scan(&mut self, open_goto_line: bool) {
2507 let buffer_id = self.active_buffer();
2508 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2509 let (chunks, total_bytes) = state.buffer.prepare_line_scan();
2510 let leaves = state.buffer.piece_tree_leaves();
2511 self.line_scan_state = Some(super::LineScanState {
2512 buffer_id,
2513 leaves,
2514 chunks,
2515 next_chunk: 0,
2516 total_bytes,
2517 scanned_bytes: 0,
2518 updates: Vec::new(),
2519 open_goto_line_on_complete: open_goto_line,
2520 });
2521 self.set_status_message(t!("goto.scanning_progress", percent = 0).to_string());
2522 }
2523 }
2524
2525 pub fn process_line_scan(&mut self) -> bool {
2528 let _span = tracing::info_span!("process_line_scan").entered();
2529 let scan = match self.line_scan_state.as_mut() {
2530 Some(s) => s,
2531 None => return false,
2532 };
2533
2534 let buffer_id = scan.buffer_id;
2535
2536 if let Err(e) = self.process_line_scan_batch(buffer_id) {
2537 tracing::warn!("Line scan error: {e}");
2538 self.finish_line_scan_with_error(e);
2539 return true;
2540 }
2541
2542 let scan = self.line_scan_state.as_ref().unwrap();
2543 if scan.next_chunk >= scan.chunks.len() {
2544 self.finish_line_scan_ok();
2545 } else {
2546 let pct = if scan.total_bytes > 0 {
2547 (scan.scanned_bytes * 100) / scan.total_bytes
2548 } else {
2549 100
2550 };
2551 self.set_status_message(t!("goto.scanning_progress", percent = pct).to_string());
2552 }
2553 true
2554 }
2555
2556 fn process_line_scan_batch(&mut self, buffer_id: BufferId) -> std::io::Result<()> {
2564 let _span = tracing::info_span!("process_line_scan_batch").entered();
2565 let concurrency = self.config.editor.read_concurrency.max(1);
2566
2567 let state = self.buffers.get(&buffer_id);
2568 let scan = self.line_scan_state.as_mut().unwrap();
2569
2570 let mut results: Vec<(usize, usize)> = Vec::new();
2571 let mut io_work: Vec<(usize, std::path::PathBuf, u64, usize)> = Vec::new();
2572
2573 while scan.next_chunk < scan.chunks.len() && (results.len() + io_work.len()) < concurrency {
2574 let chunk = scan.chunks[scan.next_chunk].clone();
2575 scan.next_chunk += 1;
2576 scan.scanned_bytes += chunk.byte_len;
2577
2578 if chunk.already_known {
2579 continue;
2580 }
2581
2582 if let Some(state) = state {
2583 let leaf = &scan.leaves[chunk.leaf_index];
2584
2585 match state.buffer.leaf_io_params(leaf) {
2589 None => {
2590 let count = state.buffer.scan_leaf(leaf)?;
2592 results.push((chunk.leaf_index, count));
2593 }
2594 Some((path, offset, len)) => {
2595 io_work.push((chunk.leaf_index, path, offset, len));
2597 }
2598 }
2599 }
2600 }
2601
2602 if !io_work.is_empty() {
2604 let fs = match state {
2605 Some(s) => s.buffer.filesystem().clone(),
2606 None => return Ok(()),
2607 };
2608
2609 let rt = self
2610 .tokio_runtime
2611 .as_ref()
2612 .ok_or_else(|| std::io::Error::other("async runtime not available"))?;
2613
2614 let io_results: Vec<std::io::Result<(usize, usize)>> = rt.block_on(async {
2615 let mut handles = Vec::with_capacity(io_work.len());
2616 for (leaf_idx, path, offset, len) in io_work {
2617 let fs = fs.clone();
2618 handles.push(tokio::task::spawn_blocking(move || {
2619 let count = fs.count_line_feeds_in_range(&path, offset, len)?;
2620 Ok((leaf_idx, count))
2621 }));
2622 }
2623
2624 let mut results = Vec::with_capacity(handles.len());
2625 for handle in handles {
2626 results.push(handle.await.unwrap());
2627 }
2628 results
2629 });
2630
2631 for result in io_results {
2632 results.push(result?);
2633 }
2634 }
2635
2636 for (leaf_idx, count) in results {
2637 scan.updates.push((leaf_idx, count));
2638 }
2639
2640 Ok(())
2641 }
2642
2643 fn finish_line_scan_ok(&mut self) {
2644 let _span = tracing::info_span!("finish_line_scan_ok").entered();
2645 let scan = self.line_scan_state.take().unwrap();
2646 let open_goto = scan.open_goto_line_on_complete;
2647 if let Some(state) = self.buffers.get_mut(&scan.buffer_id) {
2648 let _span = tracing::info_span!(
2649 "rebuild_with_pristine_saved_root",
2650 updates = scan.updates.len()
2651 )
2652 .entered();
2653 state.buffer.rebuild_with_pristine_saved_root(&scan.updates);
2654 }
2655 self.set_status_message(t!("goto.scan_complete").to_string());
2656 if open_goto {
2657 self.open_goto_line_if_active(scan.buffer_id);
2658 }
2659 }
2660
2661 fn finish_line_scan_with_error(&mut self, e: std::io::Error) {
2662 let scan = self.line_scan_state.take().unwrap();
2663 let open_goto = scan.open_goto_line_on_complete;
2664 self.set_status_message(t!("goto.scan_failed", error = e.to_string()).to_string());
2665 if open_goto {
2666 self.open_goto_line_if_active(scan.buffer_id);
2667 }
2668 }
2669
2670 fn open_goto_line_if_active(&mut self, buffer_id: BufferId) {
2671 if self.active_buffer() == buffer_id {
2672 self.start_prompt(
2673 t!("file.goto_line_prompt").to_string(),
2674 PromptType::GotoLine,
2675 );
2676 }
2677 }
2678
2679 pub fn process_search_scan(&mut self) -> bool {
2684 let scan = match self.search_scan_state.as_mut() {
2685 Some(s) => s,
2686 None => return false,
2687 };
2688
2689 let buffer_id = scan.buffer_id;
2690
2691 if let Err(e) = self.process_search_scan_batch(buffer_id) {
2692 tracing::warn!("Search scan error: {e}");
2693 let _scan = self.search_scan_state.take().unwrap();
2694 self.set_status_message(format!("Search failed: {e}"));
2695 return true;
2696 }
2697
2698 let scan = self.search_scan_state.as_ref().unwrap();
2699 if scan.next_chunk >= scan.chunks.len() || scan.capped {
2700 self.finish_search_scan();
2701 } else {
2702 let pct = if scan.total_bytes > 0 {
2703 (scan.scanned_bytes * 100) / scan.total_bytes
2704 } else {
2705 100
2706 };
2707 let match_count = scan.match_ranges.len();
2708 self.set_status_message(format!(
2709 "Searching... {}% ({} matches so far)",
2710 pct, match_count
2711 ));
2712 }
2713 true
2714 }
2715
2716 fn process_search_scan_batch(
2725 &mut self,
2726 buffer_id: crate::model::event::BufferId,
2727 ) -> std::io::Result<()> {
2728 let concurrency = self.config.editor.read_concurrency.max(1);
2729
2730 for _ in 0..concurrency {
2731 let (doc_offset, byte_len, overlap_tail, overlap_doc_offset, query_len) = {
2733 let scan = match self.search_scan_state.as_mut() {
2734 Some(s) => s,
2735 None => return Ok(()),
2736 };
2737 if scan.next_chunk >= scan.chunks.len() || scan.capped {
2738 break;
2739 }
2740 let chunk = scan.chunks[scan.next_chunk].clone();
2741 scan.next_chunk += 1;
2742 scan.scanned_bytes += chunk.byte_len;
2743
2744 let doc_offset = scan.next_doc_offset;
2745 scan.next_doc_offset += chunk.byte_len;
2746
2747 let overlap_tail = scan.overlap_tail.clone();
2748 let overlap_doc_offset = scan.overlap_doc_offset;
2749 let query_len = scan.query.len();
2750
2751 (
2752 doc_offset,
2753 chunk.byte_len,
2754 overlap_tail,
2755 overlap_doc_offset,
2756 query_len,
2757 )
2758 };
2759
2760 let chunk_bytes: Vec<u8> = if let Some(state) = self.buffers.get_mut(&buffer_id) {
2762 match state.buffer.get_text_range_mut(doc_offset, byte_len) {
2763 Ok(bytes) => bytes,
2764 Err(e) => {
2765 tracing::warn!("Failed to read chunk at offset {}: {}", doc_offset, e);
2766 continue;
2767 }
2768 }
2769 } else {
2770 continue;
2771 };
2772
2773 let scan = self.search_scan_state.as_mut().unwrap();
2775
2776 let overlap_len = overlap_tail.len();
2777 let mut search_buf = Vec::with_capacity(overlap_len + chunk_bytes.len());
2778 search_buf.extend_from_slice(&overlap_tail);
2779 search_buf.extend_from_slice(&chunk_bytes);
2780
2781 let buf_doc_offset = if overlap_len > 0 {
2782 overlap_doc_offset
2783 } else {
2784 doc_offset
2785 };
2786
2787 let search_text = String::from_utf8_lossy(&search_buf);
2788 for m in scan.regex.find_iter(&search_text) {
2789 let match_doc_offset = buf_doc_offset + m.start();
2790 let match_len = m.end() - m.start();
2791
2792 if overlap_len > 0 && m.end() <= overlap_len {
2794 continue;
2795 }
2796
2797 if scan.match_ranges.len() >= SearchState::MAX_MATCHES {
2798 scan.capped = true;
2799 break;
2800 }
2801
2802 scan.match_ranges.push((match_doc_offset, match_len));
2803 }
2804
2805 let max_overlap = query_len.max(256).min(chunk_bytes.len());
2807 let tail_start = chunk_bytes.len().saturating_sub(max_overlap);
2808 scan.overlap_tail = chunk_bytes[tail_start..].to_vec();
2809 scan.overlap_doc_offset = doc_offset + tail_start;
2810 }
2811
2812 Ok(())
2813 }
2814
2815 fn finish_search_scan(&mut self) {
2819 let scan = self.search_scan_state.take().unwrap();
2820 let buffer_id = scan.buffer_id;
2821 let match_ranges = scan.match_ranges;
2822 let capped = scan.capped;
2823 let query = scan.query;
2824
2825 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2829 state.buffer.refresh_saved_root_if_unmodified();
2830 }
2831
2832 if match_ranges.is_empty() {
2833 self.search_state = None;
2834 self.set_status_message(format!("No matches found for '{}'", query));
2835 return;
2836 }
2837
2838 self.finalize_search(&query, match_ranges, capped, None);
2839 }
2840}