1use std::path::Path;
15use std::sync::Arc;
16
17use anyhow::Result as AnyhowResult;
18use rust_i18n::t;
19
20use crate::model::event::{BufferId, LeafId};
21use crate::state::EditorState;
22
23use super::Editor;
24
25impl Editor {
26 pub fn open_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
31 let active_had_path = self
35 .buffers
36 .get(&self.active_buffer())
37 .and_then(|s| s.buffer.file_path())
38 .is_some();
39
40 let buffer_id = self.open_file_no_focus(path)?;
41
42 let is_new_buffer = self.active_buffer() != buffer_id;
46
47 if is_new_buffer && !self.suppress_position_history_once {
48 self.position_history.commit_pending_movement();
50
51 let cursors = self.active_cursors();
53 let position = cursors.primary().position;
54 let anchor = cursors.primary().anchor;
55 self.position_history
56 .record_movement(self.active_buffer(), position, anchor);
57 self.position_history.commit_pending_movement();
58 }
59
60 self.set_active_buffer(buffer_id);
61
62 if !is_new_buffer && !active_had_path {
69 #[cfg(feature = "plugins")]
70 self.update_plugin_state_snapshot();
71
72 self.plugin_manager.run_hook(
73 "buffer_activated",
74 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
75 );
76 }
77
78 let display_name = self
80 .buffer_metadata
81 .get(&buffer_id)
82 .map(|m| m.display_name.clone())
83 .unwrap_or_else(|| path.display().to_string());
84
85 let is_binary = self
87 .buffers
88 .get(&buffer_id)
89 .map(|s| s.buffer.is_binary())
90 .unwrap_or(false);
91
92 if is_binary {
94 self.status_message = Some(t!("buffer.opened_binary", name = display_name).to_string());
95 } else {
96 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
97 }
98
99 Ok(buffer_id)
100 }
101
102 pub fn open_file_no_focus(&mut self, path: &Path) -> anyhow::Result<BufferId> {
109 if !self.filesystem.is_remote_connected() {
112 anyhow::bail!(
113 "Cannot open file: remote connection lost ({})",
114 self.filesystem
115 .remote_connection_info()
116 .unwrap_or("unknown host")
117 );
118 }
119
120 let base_dir = if self.filesystem.remote_connection_info().is_some() {
123 self.filesystem
124 .home_dir()
125 .unwrap_or_else(|_| self.working_dir.clone())
126 } else {
127 self.working_dir.clone()
128 };
129
130 let resolved_path = if path.is_relative() {
131 base_dir.join(path)
132 } else {
133 path.to_path_buf()
134 };
135
136 let file_exists = self.filesystem.exists(&resolved_path);
139
140 let display_path = resolved_path.clone();
144
145 let canonical_path = if file_exists {
149 self.filesystem
150 .canonicalize(&resolved_path)
151 .unwrap_or_else(|_| resolved_path.clone())
152 } else {
153 if let Some(parent) = resolved_path.parent() {
155 let canonical_parent = if parent.as_os_str().is_empty() {
156 base_dir.clone()
158 } else {
159 self.filesystem
160 .canonicalize(parent)
161 .unwrap_or_else(|_| parent.to_path_buf())
162 };
163 if let Some(filename) = resolved_path.file_name() {
164 canonical_parent.join(filename)
165 } else {
166 resolved_path
167 }
168 } else {
169 resolved_path
170 }
171 };
172 let path = canonical_path.as_path();
173
174 if self.filesystem.is_dir(path).unwrap_or(false) {
178 anyhow::bail!(t!("buffer.cannot_open_directory"));
179 }
180
181 let already_open = self
183 .buffers
184 .iter()
185 .find(|(_, state)| state.buffer.file_path() == Some(path))
186 .map(|(id, _)| *id);
187
188 if let Some(id) = already_open {
189 return Ok(id);
190 }
191
192 let replace_current = {
195 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
196 !current_state.is_composite_buffer
197 && current_state.buffer.is_empty()
198 && !current_state.buffer.is_modified()
199 && current_state.buffer.file_path().is_none()
200 };
201
202 let buffer_id = if replace_current {
203 self.active_buffer()
205 } else {
206 let id = BufferId(self.next_buffer_id);
208 self.next_buffer_id += 1;
209 id
210 };
211
212 tracing::info!(
214 "[SYNTAX DEBUG] open_file_no_focus: path={:?}, extension={:?}, catalog={}",
215 path,
216 path.extension(),
217 self.grammar_registry.catalog().len(),
218 );
219 let mut state = if file_exists {
220 let buffer = crate::model::buffer::Buffer::load_from_file(
223 &canonical_path,
224 self.config.editor.large_file_threshold_bytes as usize,
225 Arc::clone(&self.filesystem),
226 )?;
227 let detected =
228 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
229 &display_path,
230 &self.grammar_registry,
231 &self.config.languages,
232 self.config.default_language.as_deref(),
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 =
263 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
264 state.buffer_settings.tab_size =
266 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
267 if state.buffer_settings.auto_close {
269 if let Some(lang_auto_close) = lang_config.auto_close {
270 state.buffer_settings.auto_close = lang_auto_close;
271 }
272 }
273 if state.buffer_settings.auto_surround {
275 if let Some(lang_auto_surround) = lang_config.auto_surround {
276 state.buffer_settings.auto_surround = lang_auto_surround;
277 }
278 }
279 } else {
280 state.buffer_settings.tab_size = self.config.editor.tab_size;
281 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
282 }
283 state.buffer_settings.whitespace = whitespace;
284
285 state
287 .margins
288 .configure_for_line_numbers(self.config.editor.line_numbers);
289
290 self.buffers.insert(buffer_id, state);
291 self.event_logs
292 .insert(buffer_id, crate::model::event::EventLog::new());
293
294 let mut metadata = super::types::BufferMetadata::with_file(
296 path.to_path_buf(),
297 &display_path,
298 &self.working_dir,
299 );
300
301 if is_binary {
303 metadata.binary = true;
304 metadata.read_only = true;
305 metadata.disable_lsp(t!("buffer.binary_file").to_string());
306 }
307
308 if file_exists && !metadata.read_only && !self.filesystem.is_writable(path) {
310 metadata.read_only = true;
311 }
312
313 if metadata.read_only {
315 if let Some(state) = self.buffers.get_mut(&buffer_id) {
316 state.editing_disabled = true;
317 }
318 }
319
320 if !is_binary {
322 self.notify_lsp_file_opened(path, buffer_id, &mut metadata);
323 }
324
325 self.buffer_metadata.insert(buffer_id, metadata);
327
328 let target_split = self.preferred_split_for_file();
331 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
332 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
333 let page_view = self.resolve_page_view_for_buffer(buffer_id);
334 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
335 view_state.add_buffer(buffer_id);
336 let buf_state = view_state.ensure_buffer_state(buffer_id);
338 buf_state.apply_config_defaults(
339 self.config.editor.line_numbers,
340 self.config.editor.highlight_current_line,
341 line_wrap,
342 self.config.editor.wrap_indent,
343 wrap_column,
344 self.config.editor.rulers.clone(),
345 );
346 if let Some(page_width) = page_view {
348 buf_state.activate_page_view(page_width);
349 }
350 }
351
352 self.restore_global_file_state(buffer_id, path, target_split);
355
356 self.emit_event(
358 crate::model::control_event::events::FILE_OPENED.name,
359 serde_json::json!({
360 "path": path.display().to_string(),
361 "buffer_id": buffer_id.0
362 }),
363 );
364
365 self.watch_file(path);
367
368 self.plugin_manager.run_hook(
370 "after_file_open",
371 crate::services::plugins::hooks::HookArgs::AfterFileOpen {
372 buffer_id,
373 path: path.to_path_buf(),
374 },
375 );
376
377 Ok(buffer_id)
378 }
379
380 pub fn open_local_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
386 let resolved_path = if path.is_relative() {
388 self.working_dir.join(path)
389 } else {
390 path.to_path_buf()
391 };
392
393 let display_path = resolved_path.clone();
395
396 let canonical_path = resolved_path
398 .canonicalize()
399 .unwrap_or_else(|_| resolved_path.clone());
400 let path = canonical_path.as_path();
401
402 let already_open = self
404 .buffers
405 .iter()
406 .find(|(_, state)| state.buffer.file_path() == Some(path))
407 .map(|(id, _)| *id);
408
409 if let Some(id) = already_open {
410 self.set_active_buffer(id);
411 return Ok(id);
412 }
413
414 let buffer_id = BufferId(self.next_buffer_id);
416 self.next_buffer_id += 1;
417
418 let buffer = crate::model::buffer::Buffer::load_from_file(
421 &canonical_path,
422 self.config.editor.large_file_threshold_bytes as usize,
423 Arc::clone(&self.local_filesystem),
424 )?;
425 let detected =
426 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
427 &display_path,
428 &self.grammar_registry,
429 &self.config.languages,
430 self.config.default_language.as_deref(),
431 );
432 let state = EditorState::from_buffer_with_language(buffer, detected);
433
434 self.buffers.insert(buffer_id, state);
435 self.event_logs
436 .insert(buffer_id, crate::model::event::EventLog::new());
437
438 let metadata = super::types::BufferMetadata::with_file(
440 path.to_path_buf(),
441 &display_path,
442 &self.working_dir,
443 );
444 self.buffer_metadata.insert(buffer_id, metadata);
445
446 let target_split = self.preferred_split_for_file();
448 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
449 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
450 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
451 view_state.add_buffer(buffer_id);
452 let buf_state = view_state.ensure_buffer_state(buffer_id);
453 buf_state.apply_config_defaults(
454 self.config.editor.line_numbers,
455 self.config.editor.highlight_current_line,
456 line_wrap,
457 self.config.editor.wrap_indent,
458 wrap_column,
459 self.config.editor.rulers.clone(),
460 );
461 }
462
463 self.set_active_buffer(buffer_id);
464
465 let display_name = path.display().to_string();
466 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
467
468 Ok(buffer_id)
469 }
470
471 pub fn open_file_with_encoding(
476 &mut self,
477 path: &Path,
478 encoding: crate::model::buffer::Encoding,
479 ) -> anyhow::Result<BufferId> {
480 let base_dir = self.working_dir.clone();
482
483 let resolved_path = if path.is_relative() {
484 base_dir.join(path)
485 } else {
486 path.to_path_buf()
487 };
488
489 let display_path = resolved_path.clone();
491
492 let canonical_path = self
494 .filesystem
495 .canonicalize(&resolved_path)
496 .unwrap_or_else(|_| resolved_path.clone());
497 let path = canonical_path.as_path();
498
499 let already_open = self
501 .buffers
502 .iter()
503 .find(|(_, state)| state.buffer.file_path() == Some(path))
504 .map(|(id, _)| *id);
505
506 if let Some(id) = already_open {
507 if let Some(state) = self.buffers.get_mut(&id) {
509 state.buffer.set_encoding(encoding);
510 }
511 self.set_active_buffer(id);
512 return Ok(id);
513 }
514
515 let buffer_id = BufferId(self.next_buffer_id);
517 self.next_buffer_id += 1;
518
519 let buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
521 path,
522 encoding,
523 Arc::clone(&self.filesystem),
524 crate::model::buffer::BufferConfig {
525 estimated_line_length: self.config.editor.estimated_line_length,
526 },
527 )?;
528 let detected =
531 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
532 &display_path,
533 &self.grammar_registry,
534 &self.config.languages,
535 self.config.default_language.as_deref(),
536 );
537
538 let mut state = EditorState::from_buffer_with_language(buffer, detected);
539
540 state
541 .margins
542 .configure_for_line_numbers(self.config.editor.line_numbers);
543
544 self.buffers.insert(buffer_id, state);
545 self.event_logs
546 .insert(buffer_id, crate::model::event::EventLog::new());
547
548 let metadata = super::types::BufferMetadata::with_file(
549 path.to_path_buf(),
550 &display_path,
551 &self.working_dir,
552 );
553 self.buffer_metadata.insert(buffer_id, metadata);
554
555 let target_split = self.preferred_split_for_file();
557 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
558 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
559 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
560 view_state.add_buffer(buffer_id);
561 let buf_state = view_state.ensure_buffer_state(buffer_id);
562 buf_state.apply_config_defaults(
563 self.config.editor.line_numbers,
564 self.config.editor.highlight_current_line,
565 line_wrap,
566 self.config.editor.wrap_indent,
567 wrap_column,
568 self.config.editor.rulers.clone(),
569 );
570 }
571
572 self.set_active_buffer(buffer_id);
573
574 Ok(buffer_id)
575 }
576
577 pub fn reload_with_encoding(
581 &mut self,
582 encoding: crate::model::buffer::Encoding,
583 ) -> anyhow::Result<()> {
584 let buffer_id = self.active_buffer();
585
586 let path = self
588 .buffers
589 .get(&buffer_id)
590 .and_then(|s| s.buffer.file_path().map(|p| p.to_path_buf()))
591 .ok_or_else(|| anyhow::anyhow!("Buffer has no file path"))?;
592
593 if let Some(state) = self.buffers.get(&buffer_id) {
595 if state.buffer.is_modified() {
596 anyhow::bail!("Cannot reload: buffer has unsaved modifications");
597 }
598 }
599
600 let new_buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
602 &path,
603 encoding,
604 Arc::clone(&self.filesystem),
605 crate::model::buffer::BufferConfig {
606 estimated_line_length: self.config.editor.estimated_line_length,
607 },
608 )?;
609
610 if let Some(state) = self.buffers.get_mut(&buffer_id) {
612 state.buffer = new_buffer;
613 state.highlighter.invalidate_all();
615 }
616
617 let split_id = self.split_manager.active_split();
619 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
620 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
621 buf_state.cursors = crate::model::cursor::Cursors::new();
622 }
623 }
624
625 Ok(())
626 }
627
628 pub fn open_file_large_encoding_confirmed(&mut self, path: &Path) -> anyhow::Result<BufferId> {
633 let base_dir = self.working_dir.clone();
635
636 let resolved_path = if path.is_relative() {
637 base_dir.join(path)
638 } else {
639 path.to_path_buf()
640 };
641
642 let display_path = resolved_path.clone();
644
645 let canonical_path = self
647 .filesystem
648 .canonicalize(&resolved_path)
649 .unwrap_or_else(|_| resolved_path.clone());
650 let path = canonical_path.as_path();
651
652 let already_open = self
654 .buffers
655 .iter()
656 .find(|(_, state)| state.buffer.file_path() == Some(path))
657 .map(|(id, _)| *id);
658
659 if let Some(id) = already_open {
660 self.set_active_buffer(id);
661 return Ok(id);
662 }
663
664 let buffer_id = BufferId(self.next_buffer_id);
666 self.next_buffer_id += 1;
667
668 let buffer = crate::model::buffer::Buffer::load_large_file_confirmed(
670 path,
671 Arc::clone(&self.filesystem),
672 )?;
673 let detected =
676 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
677 &display_path,
678 &self.grammar_registry,
679 &self.config.languages,
680 self.config.default_language.as_deref(),
681 );
682
683 let mut state = EditorState::from_buffer_with_language(buffer, detected);
684
685 state
686 .margins
687 .configure_for_line_numbers(self.config.editor.line_numbers);
688
689 self.buffers.insert(buffer_id, state);
690 self.event_logs
691 .insert(buffer_id, crate::model::event::EventLog::new());
692
693 let metadata = super::types::BufferMetadata::with_file(
694 path.to_path_buf(),
695 &display_path,
696 &self.working_dir,
697 );
698 self.buffer_metadata.insert(buffer_id, metadata);
699
700 let target_split = self.preferred_split_for_file();
702 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
703 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
704 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
705 view_state.add_buffer(buffer_id);
706 let buf_state = view_state.ensure_buffer_state(buffer_id);
707 buf_state.apply_config_defaults(
708 self.config.editor.line_numbers,
709 self.config.editor.highlight_current_line,
710 line_wrap,
711 self.config.editor.wrap_indent,
712 wrap_column,
713 self.config.editor.rulers.clone(),
714 );
715 }
716
717 self.set_active_buffer(buffer_id);
718
719 let display_name = self
721 .buffer_metadata
722 .get(&buffer_id)
723 .map(|m| m.display_name.clone())
724 .unwrap_or_else(|| path.display().to_string());
725
726 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
727
728 Ok(buffer_id)
729 }
730
731 fn restore_global_file_state(&mut self, buffer_id: BufferId, path: &Path, split_id: LeafId) {
736 use crate::workspace::PersistedFileWorkspace;
737
738 let file_state = match PersistedFileWorkspace::load(path) {
740 Some(state) => state,
741 None => return, };
743
744 let max_pos = match self.buffers.get(&buffer_id) {
746 Some(buffer) => buffer.buffer.len(),
747 None => return,
748 };
749
750 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
752 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
753 let cursor_pos = file_state.cursor.position.min(max_pos);
754 buf_state.cursors.primary_mut().position = cursor_pos;
755 buf_state.cursors.primary_mut().anchor =
756 file_state.cursor.anchor.map(|a| a.min(max_pos));
757 }
758 view_state.viewport.top_byte = file_state.scroll.top_byte;
759 view_state.viewport.left_column = file_state.scroll.left_column;
760 }
761 }
762
763 pub(super) fn save_file_state_on_close(&self, buffer_id: BufferId) {
765 use crate::workspace::{
766 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
767 };
768
769 let abs_path = match self.buffer_metadata.get(&buffer_id) {
771 Some(metadata) => match metadata.file_path() {
772 Some(path) => path.to_path_buf(),
773 None => return, },
775 None => return,
776 };
777
778 let view_state = self
780 .split_view_states
781 .values()
782 .find(|vs| vs.has_buffer(buffer_id));
783
784 let view_state = match view_state {
785 Some(vs) => vs,
786 None => return, };
788
789 let buf_state = match view_state.keyed_states.get(&buffer_id) {
791 Some(bs) => bs,
792 None => return,
793 };
794
795 let primary_cursor = buf_state.cursors.primary();
797 let file_state = SerializedFileState {
798 cursor: SerializedCursor {
799 position: primary_cursor.position,
800 anchor: primary_cursor.anchor,
801 sticky_column: primary_cursor.sticky_column,
802 },
803 additional_cursors: buf_state
804 .cursors
805 .iter()
806 .skip(1)
807 .map(|(_, cursor)| SerializedCursor {
808 position: cursor.position,
809 anchor: cursor.anchor,
810 sticky_column: cursor.sticky_column,
811 })
812 .collect(),
813 scroll: SerializedScroll {
814 top_byte: buf_state.viewport.top_byte,
815 top_view_line_offset: buf_state.viewport.top_view_line_offset,
816 left_column: buf_state.viewport.left_column,
817 },
818 view_mode: Default::default(),
819 compose_width: None,
820 plugin_state: std::collections::HashMap::new(),
821 folds: Vec::new(),
822 };
823
824 PersistedFileWorkspace::save(&abs_path, file_state);
826 tracing::debug!("Saved file state on close for {:?}", abs_path);
827 }
828}