1use std::path::Path;
15use std::sync::Arc;
16
17use rust_i18n::t;
18
19use crate::model::event::{BufferId, LeafId};
20use crate::state::EditorState;
21
22use super::Editor;
23
24impl Editor {
25 pub fn open_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
30 let active_had_path = self
34 .buffers
35 .get(&self.active_buffer())
36 .and_then(|s| s.buffer.file_path())
37 .is_some();
38
39 let buffer_id = self.open_file_no_focus(path)?;
40
41 let is_new_buffer = self.active_buffer() != buffer_id;
45
46 if is_new_buffer && !self.suppress_position_history_once {
47 self.position_history.commit_pending_movement();
49
50 let cursors = self.active_cursors();
52 let position = cursors.primary().position;
53 let anchor = cursors.primary().anchor;
54 self.position_history
55 .record_movement(self.active_buffer(), position, anchor);
56 self.position_history.commit_pending_movement();
57 }
58
59 self.set_active_buffer(buffer_id);
60
61 if !is_new_buffer && !active_had_path {
68 #[cfg(feature = "plugins")]
69 self.update_plugin_state_snapshot();
70
71 self.plugin_manager.run_hook(
72 "buffer_activated",
73 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
74 );
75 }
76
77 let display_name = self
79 .buffer_metadata
80 .get(&buffer_id)
81 .map(|m| m.display_name.clone())
82 .unwrap_or_else(|| path.display().to_string());
83
84 let is_binary = self
86 .buffers
87 .get(&buffer_id)
88 .map(|s| s.buffer.is_binary())
89 .unwrap_or(false);
90
91 if is_binary {
93 self.status_message = Some(t!("buffer.opened_binary", name = display_name).to_string());
94 } else {
95 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
96 }
97
98 Ok(buffer_id)
99 }
100
101 pub fn open_file_no_focus(&mut self, path: &Path) -> anyhow::Result<BufferId> {
108 self.open_file_no_focus_inner(path, true)
109 }
110
111 pub(super) fn open_file_for_preview(&mut self, path: &Path) -> anyhow::Result<BufferId> {
123 self.open_file_no_focus_inner(path, false)
124 }
125
126 fn open_file_no_focus_inner(
127 &mut self,
128 path: &Path,
129 allow_replace_empty: bool,
130 ) -> anyhow::Result<BufferId> {
131 if !self.authority.filesystem.is_remote_connected() {
134 anyhow::bail!(
135 "Cannot open file: remote connection lost ({})",
136 self.authority
137 .filesystem
138 .remote_connection_info()
139 .unwrap_or("unknown host")
140 );
141 }
142
143 let base_dir = if self.authority.filesystem.remote_connection_info().is_some() {
146 self.authority
147 .filesystem
148 .home_dir()
149 .unwrap_or_else(|_| self.working_dir.clone())
150 } else {
151 self.working_dir.clone()
152 };
153
154 let resolved_path = if path.is_relative() {
155 base_dir.join(path)
156 } else {
157 path.to_path_buf()
158 };
159
160 let file_exists = self.authority.filesystem.exists(&resolved_path);
163
164 let display_path = resolved_path.clone();
168
169 let canonical_path = if file_exists {
173 self.authority
174 .filesystem
175 .canonicalize(&resolved_path)
176 .unwrap_or_else(|_| resolved_path.clone())
177 } else {
178 if let Some(parent) = resolved_path.parent() {
180 let canonical_parent = if parent.as_os_str().is_empty() {
181 base_dir.clone()
183 } else {
184 self.authority
185 .filesystem
186 .canonicalize(parent)
187 .unwrap_or_else(|_| parent.to_path_buf())
188 };
189 if let Some(filename) = resolved_path.file_name() {
190 canonical_parent.join(filename)
191 } else {
192 resolved_path
193 }
194 } else {
195 resolved_path
196 }
197 };
198 let path = canonical_path.as_path();
199
200 if self.authority.filesystem.is_dir(path).unwrap_or(false) {
204 anyhow::bail!(t!("buffer.cannot_open_directory"));
205 }
206
207 let already_open = self
209 .buffers
210 .iter()
211 .find(|(_, state)| state.buffer.file_path() == Some(path))
212 .map(|(id, _)| *id);
213
214 if let Some(id) = already_open {
215 return Ok(id);
216 }
217
218 let replace_current = allow_replace_empty && {
223 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
224 !current_state.is_composite_buffer
225 && current_state.buffer.is_empty()
226 && !current_state.buffer.is_modified()
227 && current_state.buffer.file_path().is_none()
228 };
229
230 let buffer_id = if replace_current {
231 self.active_buffer()
233 } else {
234 let id = BufferId(self.next_buffer_id);
236 self.next_buffer_id += 1;
237 id
238 };
239
240 tracing::info!(
242 "[SYNTAX DEBUG] open_file_no_focus: path={:?}, extension={:?}, catalog={}",
243 path,
244 path.extension(),
245 self.grammar_registry.catalog().len(),
246 );
247 let mut state = if file_exists {
248 let buffer = crate::model::buffer::Buffer::load_from_file(
251 &canonical_path,
252 self.config.editor.large_file_threshold_bytes as usize,
253 Arc::clone(&self.authority.filesystem),
254 )?;
255 let first_line = buffer.first_line_lossy();
256 let detected =
257 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
258 &display_path,
259 first_line.as_deref(),
260 &self.grammar_registry,
261 &self.config.languages,
262 self.config.default_language.as_deref(),
263 );
264 EditorState::from_buffer_with_language(buffer, detected)
265 } else {
266 EditorState::new_with_path(
268 self.config.editor.large_file_threshold_bytes as usize,
269 Arc::clone(&self.authority.filesystem),
270 path.to_path_buf(),
271 )
272 };
273 let is_binary = state.buffer.is_binary();
277 if is_binary {
278 state.editing_disabled = true;
280 tracing::info!("Detected binary file: {}", path.display());
281 }
282
283 let mut whitespace =
287 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
288 state.buffer_settings.auto_close = self.config.editor.auto_close;
289 state.buffer_settings.auto_surround = self.config.editor.auto_surround;
290 if let Some(lang_config) = self.config.languages.get(&state.language) {
291 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
292 state.buffer_settings.use_tabs =
293 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
294 state.buffer_settings.tab_size =
296 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
297 if state.buffer_settings.auto_close {
299 if let Some(lang_auto_close) = lang_config.auto_close {
300 state.buffer_settings.auto_close = lang_auto_close;
301 }
302 }
303 if state.buffer_settings.auto_surround {
305 if let Some(lang_auto_surround) = lang_config.auto_surround {
306 state.buffer_settings.auto_surround = lang_auto_surround;
307 }
308 }
309 } else {
310 state.buffer_settings.tab_size = self.config.editor.tab_size;
311 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
312 }
313 state.buffer_settings.whitespace = whitespace;
314
315 state
317 .margins
318 .configure_for_line_numbers(self.config.editor.line_numbers);
319
320 self.buffers.insert(buffer_id, state);
321 self.event_logs
322 .insert(buffer_id, crate::model::event::EventLog::new());
323
324 let mut metadata = super::types::BufferMetadata::with_file(
326 path.to_path_buf(),
327 &display_path,
328 &self.working_dir,
329 self.authority.path_translation.as_ref(),
330 );
331
332 if is_binary {
334 metadata.binary = true;
335 metadata.read_only = true;
336 metadata.disable_lsp(t!("buffer.binary_file").to_string());
337 }
338
339 if file_exists && !metadata.read_only && !self.authority.filesystem.is_writable(path) {
341 metadata.read_only = true;
342 }
343
344 if metadata.read_only {
346 if let Some(state) = self.buffers.get_mut(&buffer_id) {
347 state.editing_disabled = true;
348 }
349 }
350
351 if !is_binary {
353 self.notify_lsp_file_opened(path, buffer_id, &mut metadata);
354 }
355
356 self.buffer_metadata.insert(buffer_id, metadata);
358
359 let target_split = self.preferred_split_for_file();
362 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
363 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
364 let page_view = self.resolve_page_view_for_buffer(buffer_id);
365 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
366 view_state.add_buffer(buffer_id);
367 let buf_state = view_state.ensure_buffer_state(buffer_id);
369 buf_state.apply_config_defaults(
370 self.config.editor.line_numbers,
371 self.config.editor.highlight_current_line,
372 line_wrap,
373 self.config.editor.wrap_indent,
374 wrap_column,
375 self.config.editor.rulers.clone(),
376 );
377 if let Some(page_width) = page_view {
379 buf_state.activate_page_view(page_width);
380 }
381 }
382
383 self.restore_global_file_state(buffer_id, path, target_split);
386
387 self.emit_event(
389 crate::model::control_event::events::FILE_OPENED.name,
390 serde_json::json!({
391 "path": path.display().to_string(),
392 "buffer_id": buffer_id.0
393 }),
394 );
395
396 self.watch_file(path);
398
399 self.plugin_manager.run_hook(
401 "after_file_open",
402 crate::services::plugins::hooks::HookArgs::AfterFileOpen {
403 buffer_id,
404 path: path.to_path_buf(),
405 },
406 );
407
408 Ok(buffer_id)
409 }
410
411 pub fn open_local_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
417 let resolved_path = if path.is_relative() {
419 self.working_dir.join(path)
420 } else {
421 path.to_path_buf()
422 };
423
424 let display_path = resolved_path.clone();
426
427 let canonical_path = resolved_path
429 .canonicalize()
430 .unwrap_or_else(|_| resolved_path.clone());
431 let path = canonical_path.as_path();
432
433 let already_open = self
435 .buffers
436 .iter()
437 .find(|(_, state)| state.buffer.file_path() == Some(path))
438 .map(|(id, _)| *id);
439
440 if let Some(id) = already_open {
441 self.set_active_buffer(id);
442 return Ok(id);
443 }
444
445 let buffer_id = BufferId(self.next_buffer_id);
447 self.next_buffer_id += 1;
448
449 let buffer = crate::model::buffer::Buffer::load_from_file(
452 &canonical_path,
453 self.config.editor.large_file_threshold_bytes as usize,
454 Arc::clone(&self.local_filesystem),
455 )?;
456 let first_line = buffer.first_line_lossy();
457 let detected =
458 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
459 &display_path,
460 first_line.as_deref(),
461 &self.grammar_registry,
462 &self.config.languages,
463 self.config.default_language.as_deref(),
464 );
465 let state = EditorState::from_buffer_with_language(buffer, detected);
466
467 self.buffers.insert(buffer_id, state);
468 self.event_logs
469 .insert(buffer_id, crate::model::event::EventLog::new());
470
471 let metadata = super::types::BufferMetadata::with_file(
473 path.to_path_buf(),
474 &display_path,
475 &self.working_dir,
476 self.authority.path_translation.as_ref(),
477 );
478 self.buffer_metadata.insert(buffer_id, metadata);
479
480 let target_split = self.preferred_split_for_file();
482 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
483 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
484 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
485 view_state.add_buffer(buffer_id);
486 let buf_state = view_state.ensure_buffer_state(buffer_id);
487 buf_state.apply_config_defaults(
488 self.config.editor.line_numbers,
489 self.config.editor.highlight_current_line,
490 line_wrap,
491 self.config.editor.wrap_indent,
492 wrap_column,
493 self.config.editor.rulers.clone(),
494 );
495 }
496
497 self.set_active_buffer(buffer_id);
498
499 let display_name = path.display().to_string();
500 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
501
502 Ok(buffer_id)
503 }
504
505 pub fn open_file_with_encoding(
510 &mut self,
511 path: &Path,
512 encoding: crate::model::buffer::Encoding,
513 ) -> anyhow::Result<BufferId> {
514 let base_dir = self.working_dir.clone();
516
517 let resolved_path = if path.is_relative() {
518 base_dir.join(path)
519 } else {
520 path.to_path_buf()
521 };
522
523 let display_path = resolved_path.clone();
525
526 let canonical_path = self
528 .authority
529 .filesystem
530 .canonicalize(&resolved_path)
531 .unwrap_or_else(|_| resolved_path.clone());
532 let path = canonical_path.as_path();
533
534 let already_open = self
536 .buffers
537 .iter()
538 .find(|(_, state)| state.buffer.file_path() == Some(path))
539 .map(|(id, _)| *id);
540
541 if let Some(id) = already_open {
542 if let Some(state) = self.buffers.get_mut(&id) {
544 state.buffer.set_encoding(encoding);
545 }
546 self.set_active_buffer(id);
547 return Ok(id);
548 }
549
550 let buffer_id = BufferId(self.next_buffer_id);
552 self.next_buffer_id += 1;
553
554 let buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
556 path,
557 encoding,
558 Arc::clone(&self.authority.filesystem),
559 crate::model::buffer::BufferConfig {
560 estimated_line_length: self.config.editor.estimated_line_length,
561 },
562 )?;
563 let first_line = buffer.first_line_lossy();
564 let detected =
567 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
568 &display_path,
569 first_line.as_deref(),
570 &self.grammar_registry,
571 &self.config.languages,
572 self.config.default_language.as_deref(),
573 );
574
575 let mut state = EditorState::from_buffer_with_language(buffer, detected);
576
577 state
578 .margins
579 .configure_for_line_numbers(self.config.editor.line_numbers);
580
581 self.buffers.insert(buffer_id, state);
582 self.event_logs
583 .insert(buffer_id, crate::model::event::EventLog::new());
584
585 let metadata = super::types::BufferMetadata::with_file(
586 path.to_path_buf(),
587 &display_path,
588 &self.working_dir,
589 self.authority.path_translation.as_ref(),
590 );
591 self.buffer_metadata.insert(buffer_id, metadata);
592
593 let target_split = self.preferred_split_for_file();
595 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
596 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
597 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
598 view_state.add_buffer(buffer_id);
599 let buf_state = view_state.ensure_buffer_state(buffer_id);
600 buf_state.apply_config_defaults(
601 self.config.editor.line_numbers,
602 self.config.editor.highlight_current_line,
603 line_wrap,
604 self.config.editor.wrap_indent,
605 wrap_column,
606 self.config.editor.rulers.clone(),
607 );
608 }
609
610 self.set_active_buffer(buffer_id);
611
612 Ok(buffer_id)
613 }
614
615 pub fn reload_with_encoding(
619 &mut self,
620 encoding: crate::model::buffer::Encoding,
621 ) -> anyhow::Result<()> {
622 let buffer_id = self.active_buffer();
623
624 let path = self
626 .buffers
627 .get(&buffer_id)
628 .and_then(|s| s.buffer.file_path().map(|p| p.to_path_buf()))
629 .ok_or_else(|| anyhow::anyhow!("Buffer has no file path"))?;
630
631 if let Some(state) = self.buffers.get(&buffer_id) {
633 if state.buffer.is_modified() {
634 anyhow::bail!("Cannot reload: buffer has unsaved modifications");
635 }
636 }
637
638 let new_buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
640 &path,
641 encoding,
642 Arc::clone(&self.authority.filesystem),
643 crate::model::buffer::BufferConfig {
644 estimated_line_length: self.config.editor.estimated_line_length,
645 },
646 )?;
647
648 if let Some(state) = self.buffers.get_mut(&buffer_id) {
650 state.buffer = new_buffer;
651 state.highlighter.invalidate_all();
653 }
654
655 let split_id = self.split_manager.active_split();
657 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
658 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
659 buf_state.cursors = crate::model::cursor::Cursors::new();
660 }
661 }
662
663 Ok(())
664 }
665
666 pub fn open_file_large_encoding_confirmed(&mut self, path: &Path) -> anyhow::Result<BufferId> {
671 let base_dir = self.working_dir.clone();
673
674 let resolved_path = if path.is_relative() {
675 base_dir.join(path)
676 } else {
677 path.to_path_buf()
678 };
679
680 let display_path = resolved_path.clone();
682
683 let canonical_path = self
685 .authority
686 .filesystem
687 .canonicalize(&resolved_path)
688 .unwrap_or_else(|_| resolved_path.clone());
689 let path = canonical_path.as_path();
690
691 let already_open = self
693 .buffers
694 .iter()
695 .find(|(_, state)| state.buffer.file_path() == Some(path))
696 .map(|(id, _)| *id);
697
698 if let Some(id) = already_open {
699 self.set_active_buffer(id);
700 return Ok(id);
701 }
702
703 let buffer_id = BufferId(self.next_buffer_id);
705 self.next_buffer_id += 1;
706
707 let buffer = crate::model::buffer::Buffer::load_large_file_confirmed(
709 path,
710 Arc::clone(&self.authority.filesystem),
711 )?;
712 let first_line = buffer.first_line_lossy();
713 let detected =
716 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
717 &display_path,
718 first_line.as_deref(),
719 &self.grammar_registry,
720 &self.config.languages,
721 self.config.default_language.as_deref(),
722 );
723
724 let mut state = EditorState::from_buffer_with_language(buffer, detected);
725
726 state
727 .margins
728 .configure_for_line_numbers(self.config.editor.line_numbers);
729
730 self.buffers.insert(buffer_id, state);
731 self.event_logs
732 .insert(buffer_id, crate::model::event::EventLog::new());
733
734 let metadata = super::types::BufferMetadata::with_file(
735 path.to_path_buf(),
736 &display_path,
737 &self.working_dir,
738 self.authority.path_translation.as_ref(),
739 );
740 self.buffer_metadata.insert(buffer_id, metadata);
741
742 let target_split = self.preferred_split_for_file();
744 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
745 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
746 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
747 view_state.add_buffer(buffer_id);
748 let buf_state = view_state.ensure_buffer_state(buffer_id);
749 buf_state.apply_config_defaults(
750 self.config.editor.line_numbers,
751 self.config.editor.highlight_current_line,
752 line_wrap,
753 self.config.editor.wrap_indent,
754 wrap_column,
755 self.config.editor.rulers.clone(),
756 );
757 }
758
759 self.set_active_buffer(buffer_id);
760
761 let display_name = self
763 .buffer_metadata
764 .get(&buffer_id)
765 .map(|m| m.display_name.clone())
766 .unwrap_or_else(|| path.display().to_string());
767
768 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
769
770 Ok(buffer_id)
771 }
772
773 fn restore_global_file_state(&mut self, buffer_id: BufferId, path: &Path, split_id: LeafId) {
778 use crate::workspace::PersistedFileWorkspace;
779
780 let file_state = match PersistedFileWorkspace::load(path) {
782 Some(state) => state,
783 None => return, };
785
786 let max_pos = match self.buffers.get(&buffer_id) {
788 Some(buffer) => buffer.buffer.len(),
789 None => return,
790 };
791
792 let view_state_opt = self.split_view_states.get_mut(&split_id);
803 let buffer_state_opt = self.buffers.get_mut(&buffer_id);
804 if let (Some(view_state), Some(buffer_state)) = (view_state_opt, buffer_state_opt) {
805 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
806 let cursor_pos = file_state.cursor.position.min(max_pos);
807 buf_state.cursors.primary_mut().position = cursor_pos;
808 buf_state.cursors.primary_mut().anchor =
809 file_state.cursor.anchor.map(|a| a.min(max_pos));
810 buf_state.viewport.top_byte = file_state.scroll.top_byte;
811 buf_state.viewport.left_column = file_state.scroll.left_column;
812 super::navigation::reconcile_restored_buffer_view(
821 buf_state,
822 &mut buffer_state.buffer,
823 );
824 }
825 }
826 }
827
828 pub(super) fn save_file_state_on_close(&self, buffer_id: BufferId) {
830 use crate::workspace::{
831 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
832 };
833
834 let abs_path = match self.buffer_metadata.get(&buffer_id) {
836 Some(metadata) => match metadata.file_path() {
837 Some(path) => path.to_path_buf(),
838 None => return, },
840 None => return,
841 };
842
843 let view_state = self
845 .split_view_states
846 .values()
847 .find(|vs| vs.has_buffer(buffer_id));
848
849 let view_state = match view_state {
850 Some(vs) => vs,
851 None => return, };
853
854 let buf_state = match view_state.keyed_states.get(&buffer_id) {
856 Some(bs) => bs,
857 None => return,
858 };
859
860 let primary_cursor = buf_state.cursors.primary();
862 let file_state = SerializedFileState {
863 cursor: SerializedCursor {
864 position: primary_cursor.position,
865 anchor: primary_cursor.anchor,
866 sticky_column: primary_cursor.sticky_column,
867 },
868 additional_cursors: buf_state
869 .cursors
870 .iter()
871 .skip(1)
872 .map(|(_, cursor)| SerializedCursor {
873 position: cursor.position,
874 anchor: cursor.anchor,
875 sticky_column: cursor.sticky_column,
876 })
877 .collect(),
878 scroll: SerializedScroll {
879 top_byte: buf_state.viewport.top_byte,
880 top_view_line_offset: buf_state.viewport.top_view_line_offset,
881 left_column: buf_state.viewport.left_column,
882 },
883 view_mode: Default::default(),
884 compose_width: None,
885 plugin_state: std::collections::HashMap::new(),
886 folds: Vec::new(),
887 };
888
889 PersistedFileWorkspace::save(&abs_path, file_state);
891 tracing::debug!("Saved file state on close for {:?}", abs_path);
892 }
893
894 pub(crate) fn open_lsp_uri_target(
915 &mut self,
916 uri: &crate::app::types::LspUri,
917 ) -> anyhow::Result<BufferId> {
918 let translation = self.authority.path_translation.clone();
919 let host_path = uri
920 .to_host_path(translation.as_ref())
921 .ok_or_else(|| anyhow::anyhow!("URI is not a file path"))?;
922
923 if self.authority.filesystem.exists(&host_path) {
929 return self.open_file(&host_path);
930 }
931
932 if translation.is_some() {
938 let container_path = uri.to_host_path(None).ok_or_else(|| {
943 anyhow::anyhow!("URI is not a file path (container-side decode failed)")
944 })?;
945 let buffer_id = self.fetch_and_open_container_file(container_path, uri.clone())?;
946 self.set_active_buffer(buffer_id);
950 return Ok(buffer_id);
951 }
952
953 Err(anyhow::anyhow!(
955 "could not open {}: file not found",
956 host_path.display()
957 ))
958 }
959
960 fn fetch_and_open_container_file(
970 &mut self,
971 container_path: std::path::PathBuf,
972 uri: crate::app::types::LspUri,
973 ) -> anyhow::Result<BufferId> {
974 let runtime = self.tokio_runtime.as_ref().ok_or_else(|| {
975 anyhow::anyhow!(
976 "could not open {}: no tokio runtime available for container fetch",
977 container_path.display()
978 )
979 })?;
980
981 let spawner = self.authority.process_spawner.clone();
982 let path_arg = container_path.to_string_lossy().into_owned();
983 let result = runtime
984 .block_on(spawner.spawn("cat".into(), vec![path_arg], None))
985 .map_err(|e| {
986 anyhow::anyhow!(
987 "could not open {} from container: {}",
988 container_path.display(),
989 e
990 )
991 })?;
992
993 if result.exit_code != 0 {
994 let first_stderr_line = result
995 .stderr
996 .lines()
997 .next()
998 .unwrap_or("(no error message)")
999 .trim();
1000 anyhow::bail!(
1001 "could not open {} from container: {}",
1002 container_path.display(),
1003 first_stderr_line
1004 );
1005 }
1006
1007 self.open_container_only_file(container_path, uri, result.stdout.into_bytes())
1008 }
1009
1010 pub(crate) fn open_container_only_file(
1017 &mut self,
1018 container_path: std::path::PathBuf,
1019 uri: crate::app::types::LspUri,
1020 content: Vec<u8>,
1021 ) -> anyhow::Result<BufferId> {
1022 let already_open = self
1025 .buffers
1026 .iter()
1027 .find(|(_, state)| state.buffer.file_path() == Some(container_path.as_path()))
1028 .map(|(id, _)| *id);
1029 if let Some(id) = already_open {
1030 return Ok(id);
1031 }
1032
1033 let mut buffer = crate::model::buffer::Buffer::from_bytes(
1038 content,
1039 Arc::clone(&self.authority.filesystem),
1040 );
1041 buffer.rename_file_path(container_path.clone());
1042
1043 let first_line = buffer.first_line_lossy();
1047 let detected =
1048 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
1049 &container_path,
1050 first_line.as_deref(),
1051 &self.grammar_registry,
1052 &self.config.languages,
1053 self.config.default_language.as_deref(),
1054 );
1055 let mut state = EditorState::from_buffer_with_language(buffer, detected);
1056 state.editing_disabled = true;
1057
1058 let mut whitespace =
1063 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
1064 if let Some(lang_config) = self.config.languages.get(&state.language) {
1065 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
1066 state.buffer_settings.use_tabs =
1067 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
1068 state.buffer_settings.tab_size =
1069 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
1070 } else {
1071 state.buffer_settings.tab_size = self.config.editor.tab_size;
1072 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
1073 }
1074 state.buffer_settings.whitespace = whitespace;
1075 state
1076 .margins
1077 .configure_for_line_numbers(self.config.editor.line_numbers);
1078
1079 let buffer_id = BufferId(self.next_buffer_id);
1080 self.next_buffer_id += 1;
1081 self.buffers.insert(buffer_id, state);
1082 self.event_logs
1083 .insert(buffer_id, crate::model::event::EventLog::new());
1084
1085 let mut metadata =
1086 super::types::BufferMetadata::with_container_file(container_path.clone(), uri);
1087 self.notify_lsp_file_opened(&container_path, buffer_id, &mut metadata);
1092 self.buffer_metadata.insert(buffer_id, metadata);
1093
1094 let target_split = self.preferred_split_for_file();
1098 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
1099 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
1100 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
1101 view_state.add_buffer(buffer_id);
1102 let buf_state = view_state.ensure_buffer_state(buffer_id);
1103 buf_state.apply_config_defaults(
1104 self.config.editor.line_numbers,
1105 self.config.editor.highlight_current_line,
1106 line_wrap,
1107 self.config.editor.wrap_indent,
1108 wrap_column,
1109 self.config.editor.rulers.clone(),
1110 );
1111 }
1112
1113 Ok(buffer_id)
1114 }
1115}