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 if !self.authority.filesystem.is_remote_connected() {
111 anyhow::bail!(
112 "Cannot open file: remote connection lost ({})",
113 self.authority
114 .filesystem
115 .remote_connection_info()
116 .unwrap_or("unknown host")
117 );
118 }
119
120 let base_dir = if self.authority.filesystem.remote_connection_info().is_some() {
123 self.authority
124 .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.authority.filesystem.exists(&resolved_path);
140
141 let display_path = resolved_path.clone();
145
146 let canonical_path = if file_exists {
150 self.authority
151 .filesystem
152 .canonicalize(&resolved_path)
153 .unwrap_or_else(|_| resolved_path.clone())
154 } else {
155 if let Some(parent) = resolved_path.parent() {
157 let canonical_parent = if parent.as_os_str().is_empty() {
158 base_dir.clone()
160 } else {
161 self.authority
162 .filesystem
163 .canonicalize(parent)
164 .unwrap_or_else(|_| parent.to_path_buf())
165 };
166 if let Some(filename) = resolved_path.file_name() {
167 canonical_parent.join(filename)
168 } else {
169 resolved_path
170 }
171 } else {
172 resolved_path
173 }
174 };
175 let path = canonical_path.as_path();
176
177 if self.authority.filesystem.is_dir(path).unwrap_or(false) {
181 anyhow::bail!(t!("buffer.cannot_open_directory"));
182 }
183
184 let already_open = self
186 .buffers
187 .iter()
188 .find(|(_, state)| state.buffer.file_path() == Some(path))
189 .map(|(id, _)| *id);
190
191 if let Some(id) = already_open {
192 return Ok(id);
193 }
194
195 let replace_current = {
198 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
199 !current_state.is_composite_buffer
200 && current_state.buffer.is_empty()
201 && !current_state.buffer.is_modified()
202 && current_state.buffer.file_path().is_none()
203 };
204
205 let buffer_id = if replace_current {
206 self.active_buffer()
208 } else {
209 let id = BufferId(self.next_buffer_id);
211 self.next_buffer_id += 1;
212 id
213 };
214
215 tracing::info!(
217 "[SYNTAX DEBUG] open_file_no_focus: path={:?}, extension={:?}, catalog={}",
218 path,
219 path.extension(),
220 self.grammar_registry.catalog().len(),
221 );
222 let mut state = if file_exists {
223 let buffer = crate::model::buffer::Buffer::load_from_file(
226 &canonical_path,
227 self.config.editor.large_file_threshold_bytes as usize,
228 Arc::clone(&self.authority.filesystem),
229 )?;
230 let first_line = buffer.first_line_lossy();
231 let detected =
232 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
233 &display_path,
234 first_line.as_deref(),
235 &self.grammar_registry,
236 &self.config.languages,
237 self.config.default_language.as_deref(),
238 );
239 EditorState::from_buffer_with_language(buffer, detected)
240 } else {
241 EditorState::new_with_path(
243 self.config.editor.large_file_threshold_bytes as usize,
244 Arc::clone(&self.authority.filesystem),
245 path.to_path_buf(),
246 )
247 };
248 let is_binary = state.buffer.is_binary();
252 if is_binary {
253 state.editing_disabled = true;
255 tracing::info!("Detected binary file: {}", path.display());
256 }
257
258 let mut whitespace =
262 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
263 state.buffer_settings.auto_close = self.config.editor.auto_close;
264 state.buffer_settings.auto_surround = self.config.editor.auto_surround;
265 if let Some(lang_config) = self.config.languages.get(&state.language) {
266 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
267 state.buffer_settings.use_tabs =
268 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
269 state.buffer_settings.tab_size =
271 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
272 if state.buffer_settings.auto_close {
274 if let Some(lang_auto_close) = lang_config.auto_close {
275 state.buffer_settings.auto_close = lang_auto_close;
276 }
277 }
278 if state.buffer_settings.auto_surround {
280 if let Some(lang_auto_surround) = lang_config.auto_surround {
281 state.buffer_settings.auto_surround = lang_auto_surround;
282 }
283 }
284 } else {
285 state.buffer_settings.tab_size = self.config.editor.tab_size;
286 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
287 }
288 state.buffer_settings.whitespace = whitespace;
289
290 state
292 .margins
293 .configure_for_line_numbers(self.config.editor.line_numbers);
294
295 self.buffers.insert(buffer_id, state);
296 self.event_logs
297 .insert(buffer_id, crate::model::event::EventLog::new());
298
299 let mut metadata = super::types::BufferMetadata::with_file(
301 path.to_path_buf(),
302 &display_path,
303 &self.working_dir,
304 self.authority.path_translation.as_ref(),
305 );
306
307 if is_binary {
309 metadata.binary = true;
310 metadata.read_only = true;
311 metadata.disable_lsp(t!("buffer.binary_file").to_string());
312 }
313
314 if file_exists && !metadata.read_only && !self.authority.filesystem.is_writable(path) {
316 metadata.read_only = true;
317 }
318
319 if metadata.read_only {
321 if let Some(state) = self.buffers.get_mut(&buffer_id) {
322 state.editing_disabled = true;
323 }
324 }
325
326 if !is_binary {
328 self.notify_lsp_file_opened(path, buffer_id, &mut metadata);
329 }
330
331 self.buffer_metadata.insert(buffer_id, metadata);
333
334 let target_split = self.preferred_split_for_file();
337 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
338 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
339 let page_view = self.resolve_page_view_for_buffer(buffer_id);
340 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
341 view_state.add_buffer(buffer_id);
342 let buf_state = view_state.ensure_buffer_state(buffer_id);
344 buf_state.apply_config_defaults(
345 self.config.editor.line_numbers,
346 self.config.editor.highlight_current_line,
347 line_wrap,
348 self.config.editor.wrap_indent,
349 wrap_column,
350 self.config.editor.rulers.clone(),
351 );
352 if let Some(page_width) = page_view {
354 buf_state.activate_page_view(page_width);
355 }
356 }
357
358 self.restore_global_file_state(buffer_id, path, target_split);
361
362 self.emit_event(
364 crate::model::control_event::events::FILE_OPENED.name,
365 serde_json::json!({
366 "path": path.display().to_string(),
367 "buffer_id": buffer_id.0
368 }),
369 );
370
371 self.watch_file(path);
373
374 self.plugin_manager.run_hook(
376 "after_file_open",
377 crate::services::plugins::hooks::HookArgs::AfterFileOpen {
378 buffer_id,
379 path: path.to_path_buf(),
380 },
381 );
382
383 Ok(buffer_id)
384 }
385
386 pub fn open_local_file(&mut self, path: &Path) -> anyhow::Result<BufferId> {
392 let resolved_path = if path.is_relative() {
394 self.working_dir.join(path)
395 } else {
396 path.to_path_buf()
397 };
398
399 let display_path = resolved_path.clone();
401
402 let canonical_path = resolved_path
404 .canonicalize()
405 .unwrap_or_else(|_| resolved_path.clone());
406 let path = canonical_path.as_path();
407
408 let already_open = self
410 .buffers
411 .iter()
412 .find(|(_, state)| state.buffer.file_path() == Some(path))
413 .map(|(id, _)| *id);
414
415 if let Some(id) = already_open {
416 self.set_active_buffer(id);
417 return Ok(id);
418 }
419
420 let buffer_id = BufferId(self.next_buffer_id);
422 self.next_buffer_id += 1;
423
424 let buffer = crate::model::buffer::Buffer::load_from_file(
427 &canonical_path,
428 self.config.editor.large_file_threshold_bytes as usize,
429 Arc::clone(&self.local_filesystem),
430 )?;
431 let first_line = buffer.first_line_lossy();
432 let detected =
433 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
434 &display_path,
435 first_line.as_deref(),
436 &self.grammar_registry,
437 &self.config.languages,
438 self.config.default_language.as_deref(),
439 );
440 let state = EditorState::from_buffer_with_language(buffer, detected);
441
442 self.buffers.insert(buffer_id, state);
443 self.event_logs
444 .insert(buffer_id, crate::model::event::EventLog::new());
445
446 let metadata = super::types::BufferMetadata::with_file(
448 path.to_path_buf(),
449 &display_path,
450 &self.working_dir,
451 self.authority.path_translation.as_ref(),
452 );
453 self.buffer_metadata.insert(buffer_id, metadata);
454
455 let target_split = self.preferred_split_for_file();
457 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
458 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
459 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
460 view_state.add_buffer(buffer_id);
461 let buf_state = view_state.ensure_buffer_state(buffer_id);
462 buf_state.apply_config_defaults(
463 self.config.editor.line_numbers,
464 self.config.editor.highlight_current_line,
465 line_wrap,
466 self.config.editor.wrap_indent,
467 wrap_column,
468 self.config.editor.rulers.clone(),
469 );
470 }
471
472 self.set_active_buffer(buffer_id);
473
474 let display_name = path.display().to_string();
475 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
476
477 Ok(buffer_id)
478 }
479
480 pub fn open_file_with_encoding(
485 &mut self,
486 path: &Path,
487 encoding: crate::model::buffer::Encoding,
488 ) -> anyhow::Result<BufferId> {
489 let base_dir = self.working_dir.clone();
491
492 let resolved_path = if path.is_relative() {
493 base_dir.join(path)
494 } else {
495 path.to_path_buf()
496 };
497
498 let display_path = resolved_path.clone();
500
501 let canonical_path = self
503 .authority
504 .filesystem
505 .canonicalize(&resolved_path)
506 .unwrap_or_else(|_| resolved_path.clone());
507 let path = canonical_path.as_path();
508
509 let already_open = self
511 .buffers
512 .iter()
513 .find(|(_, state)| state.buffer.file_path() == Some(path))
514 .map(|(id, _)| *id);
515
516 if let Some(id) = already_open {
517 if let Some(state) = self.buffers.get_mut(&id) {
519 state.buffer.set_encoding(encoding);
520 }
521 self.set_active_buffer(id);
522 return Ok(id);
523 }
524
525 let buffer_id = BufferId(self.next_buffer_id);
527 self.next_buffer_id += 1;
528
529 let buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
531 path,
532 encoding,
533 Arc::clone(&self.authority.filesystem),
534 crate::model::buffer::BufferConfig {
535 estimated_line_length: self.config.editor.estimated_line_length,
536 },
537 )?;
538 let first_line = buffer.first_line_lossy();
539 let detected =
542 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
543 &display_path,
544 first_line.as_deref(),
545 &self.grammar_registry,
546 &self.config.languages,
547 self.config.default_language.as_deref(),
548 );
549
550 let mut state = EditorState::from_buffer_with_language(buffer, detected);
551
552 state
553 .margins
554 .configure_for_line_numbers(self.config.editor.line_numbers);
555
556 self.buffers.insert(buffer_id, state);
557 self.event_logs
558 .insert(buffer_id, crate::model::event::EventLog::new());
559
560 let metadata = super::types::BufferMetadata::with_file(
561 path.to_path_buf(),
562 &display_path,
563 &self.working_dir,
564 self.authority.path_translation.as_ref(),
565 );
566 self.buffer_metadata.insert(buffer_id, metadata);
567
568 let target_split = self.preferred_split_for_file();
570 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
571 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
572 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
573 view_state.add_buffer(buffer_id);
574 let buf_state = view_state.ensure_buffer_state(buffer_id);
575 buf_state.apply_config_defaults(
576 self.config.editor.line_numbers,
577 self.config.editor.highlight_current_line,
578 line_wrap,
579 self.config.editor.wrap_indent,
580 wrap_column,
581 self.config.editor.rulers.clone(),
582 );
583 }
584
585 self.set_active_buffer(buffer_id);
586
587 Ok(buffer_id)
588 }
589
590 pub fn reload_with_encoding(
594 &mut self,
595 encoding: crate::model::buffer::Encoding,
596 ) -> anyhow::Result<()> {
597 let buffer_id = self.active_buffer();
598
599 let path = self
601 .buffers
602 .get(&buffer_id)
603 .and_then(|s| s.buffer.file_path().map(|p| p.to_path_buf()))
604 .ok_or_else(|| anyhow::anyhow!("Buffer has no file path"))?;
605
606 if let Some(state) = self.buffers.get(&buffer_id) {
608 if state.buffer.is_modified() {
609 anyhow::bail!("Cannot reload: buffer has unsaved modifications");
610 }
611 }
612
613 let new_buffer = crate::model::buffer::Buffer::load_from_file_with_encoding(
615 &path,
616 encoding,
617 Arc::clone(&self.authority.filesystem),
618 crate::model::buffer::BufferConfig {
619 estimated_line_length: self.config.editor.estimated_line_length,
620 },
621 )?;
622
623 if let Some(state) = self.buffers.get_mut(&buffer_id) {
625 state.buffer = new_buffer;
626 state.highlighter.invalidate_all();
628 }
629
630 let split_id = self.split_manager.active_split();
632 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
633 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
634 buf_state.cursors = crate::model::cursor::Cursors::new();
635 }
636 }
637
638 Ok(())
639 }
640
641 pub fn open_file_large_encoding_confirmed(&mut self, path: &Path) -> anyhow::Result<BufferId> {
646 let base_dir = self.working_dir.clone();
648
649 let resolved_path = if path.is_relative() {
650 base_dir.join(path)
651 } else {
652 path.to_path_buf()
653 };
654
655 let display_path = resolved_path.clone();
657
658 let canonical_path = self
660 .authority
661 .filesystem
662 .canonicalize(&resolved_path)
663 .unwrap_or_else(|_| resolved_path.clone());
664 let path = canonical_path.as_path();
665
666 let already_open = self
668 .buffers
669 .iter()
670 .find(|(_, state)| state.buffer.file_path() == Some(path))
671 .map(|(id, _)| *id);
672
673 if let Some(id) = already_open {
674 self.set_active_buffer(id);
675 return Ok(id);
676 }
677
678 let buffer_id = BufferId(self.next_buffer_id);
680 self.next_buffer_id += 1;
681
682 let buffer = crate::model::buffer::Buffer::load_large_file_confirmed(
684 path,
685 Arc::clone(&self.authority.filesystem),
686 )?;
687 let first_line = buffer.first_line_lossy();
688 let detected =
691 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
692 &display_path,
693 first_line.as_deref(),
694 &self.grammar_registry,
695 &self.config.languages,
696 self.config.default_language.as_deref(),
697 );
698
699 let mut state = EditorState::from_buffer_with_language(buffer, detected);
700
701 state
702 .margins
703 .configure_for_line_numbers(self.config.editor.line_numbers);
704
705 self.buffers.insert(buffer_id, state);
706 self.event_logs
707 .insert(buffer_id, crate::model::event::EventLog::new());
708
709 let metadata = super::types::BufferMetadata::with_file(
710 path.to_path_buf(),
711 &display_path,
712 &self.working_dir,
713 self.authority.path_translation.as_ref(),
714 );
715 self.buffer_metadata.insert(buffer_id, metadata);
716
717 let target_split = self.preferred_split_for_file();
719 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
720 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
721 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
722 view_state.add_buffer(buffer_id);
723 let buf_state = view_state.ensure_buffer_state(buffer_id);
724 buf_state.apply_config_defaults(
725 self.config.editor.line_numbers,
726 self.config.editor.highlight_current_line,
727 line_wrap,
728 self.config.editor.wrap_indent,
729 wrap_column,
730 self.config.editor.rulers.clone(),
731 );
732 }
733
734 self.set_active_buffer(buffer_id);
735
736 let display_name = self
738 .buffer_metadata
739 .get(&buffer_id)
740 .map(|m| m.display_name.clone())
741 .unwrap_or_else(|| path.display().to_string());
742
743 self.status_message = Some(t!("buffer.opened", name = display_name).to_string());
744
745 Ok(buffer_id)
746 }
747
748 fn restore_global_file_state(&mut self, buffer_id: BufferId, path: &Path, split_id: LeafId) {
753 use crate::workspace::PersistedFileWorkspace;
754
755 let file_state = match PersistedFileWorkspace::load(path) {
757 Some(state) => state,
758 None => return, };
760
761 let max_pos = match self.buffers.get(&buffer_id) {
763 Some(buffer) => buffer.buffer.len(),
764 None => return,
765 };
766
767 let view_state_opt = self.split_view_states.get_mut(&split_id);
771 let buffer_state_opt = self.buffers.get_mut(&buffer_id);
772 if let (Some(view_state), Some(buffer_state)) = (view_state_opt, buffer_state_opt) {
773 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
774 let cursor_pos = file_state.cursor.position.min(max_pos);
775 buf_state.cursors.primary_mut().position = cursor_pos;
776 buf_state.cursors.primary_mut().anchor =
777 file_state.cursor.anchor.map(|a| a.min(max_pos));
778 }
779 view_state.viewport.top_byte = file_state.scroll.top_byte;
780 view_state.viewport.left_column = file_state.scroll.left_column;
781 super::navigation::reconcile_restored_buffer_view(view_state, &mut buffer_state.buffer);
788 }
789 }
790
791 pub(super) fn save_file_state_on_close(&self, buffer_id: BufferId) {
793 use crate::workspace::{
794 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
795 };
796
797 let abs_path = match self.buffer_metadata.get(&buffer_id) {
799 Some(metadata) => match metadata.file_path() {
800 Some(path) => path.to_path_buf(),
801 None => return, },
803 None => return,
804 };
805
806 let view_state = self
808 .split_view_states
809 .values()
810 .find(|vs| vs.has_buffer(buffer_id));
811
812 let view_state = match view_state {
813 Some(vs) => vs,
814 None => return, };
816
817 let buf_state = match view_state.keyed_states.get(&buffer_id) {
819 Some(bs) => bs,
820 None => return,
821 };
822
823 let primary_cursor = buf_state.cursors.primary();
825 let file_state = SerializedFileState {
826 cursor: SerializedCursor {
827 position: primary_cursor.position,
828 anchor: primary_cursor.anchor,
829 sticky_column: primary_cursor.sticky_column,
830 },
831 additional_cursors: buf_state
832 .cursors
833 .iter()
834 .skip(1)
835 .map(|(_, cursor)| SerializedCursor {
836 position: cursor.position,
837 anchor: cursor.anchor,
838 sticky_column: cursor.sticky_column,
839 })
840 .collect(),
841 scroll: SerializedScroll {
842 top_byte: buf_state.viewport.top_byte,
843 top_view_line_offset: buf_state.viewport.top_view_line_offset,
844 left_column: buf_state.viewport.left_column,
845 },
846 view_mode: Default::default(),
847 compose_width: None,
848 plugin_state: std::collections::HashMap::new(),
849 folds: Vec::new(),
850 };
851
852 PersistedFileWorkspace::save(&abs_path, file_state);
854 tracing::debug!("Saved file state on close for {:?}", abs_path);
855 }
856
857 pub(crate) fn open_lsp_uri_target(
878 &mut self,
879 uri: &crate::app::types::LspUri,
880 ) -> anyhow::Result<BufferId> {
881 let translation = self.authority.path_translation.clone();
882 let host_path = uri
883 .to_host_path(translation.as_ref())
884 .ok_or_else(|| anyhow::anyhow!("URI is not a file path"))?;
885
886 if self.authority.filesystem.exists(&host_path) {
892 return self.open_file(&host_path);
893 }
894
895 if translation.is_some() {
901 let container_path = uri.to_host_path(None).ok_or_else(|| {
906 anyhow::anyhow!("URI is not a file path (container-side decode failed)")
907 })?;
908 let buffer_id = self.fetch_and_open_container_file(container_path, uri.clone())?;
909 self.set_active_buffer(buffer_id);
913 return Ok(buffer_id);
914 }
915
916 Err(anyhow::anyhow!(
918 "could not open {}: file not found",
919 host_path.display()
920 ))
921 }
922
923 fn fetch_and_open_container_file(
933 &mut self,
934 container_path: std::path::PathBuf,
935 uri: crate::app::types::LspUri,
936 ) -> anyhow::Result<BufferId> {
937 let runtime = self.tokio_runtime.as_ref().ok_or_else(|| {
938 anyhow::anyhow!(
939 "could not open {}: no tokio runtime available for container fetch",
940 container_path.display()
941 )
942 })?;
943
944 let spawner = self.authority.process_spawner.clone();
945 let path_arg = container_path.to_string_lossy().into_owned();
946 let result = runtime
947 .block_on(spawner.spawn("cat".into(), vec![path_arg], None))
948 .map_err(|e| {
949 anyhow::anyhow!(
950 "could not open {} from container: {}",
951 container_path.display(),
952 e
953 )
954 })?;
955
956 if result.exit_code != 0 {
957 let first_stderr_line = result
958 .stderr
959 .lines()
960 .next()
961 .unwrap_or("(no error message)")
962 .trim();
963 anyhow::bail!(
964 "could not open {} from container: {}",
965 container_path.display(),
966 first_stderr_line
967 );
968 }
969
970 self.open_container_only_file(container_path, uri, result.stdout.into_bytes())
971 }
972
973 pub(crate) fn open_container_only_file(
980 &mut self,
981 container_path: std::path::PathBuf,
982 uri: crate::app::types::LspUri,
983 content: Vec<u8>,
984 ) -> anyhow::Result<BufferId> {
985 let already_open = self
988 .buffers
989 .iter()
990 .find(|(_, state)| state.buffer.file_path() == Some(container_path.as_path()))
991 .map(|(id, _)| *id);
992 if let Some(id) = already_open {
993 return Ok(id);
994 }
995
996 let mut buffer = crate::model::buffer::Buffer::from_bytes(
1001 content,
1002 Arc::clone(&self.authority.filesystem),
1003 );
1004 buffer.rename_file_path(container_path.clone());
1005
1006 let first_line = buffer.first_line_lossy();
1010 let detected =
1011 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
1012 &container_path,
1013 first_line.as_deref(),
1014 &self.grammar_registry,
1015 &self.config.languages,
1016 self.config.default_language.as_deref(),
1017 );
1018 let mut state = EditorState::from_buffer_with_language(buffer, detected);
1019 state.editing_disabled = true;
1020
1021 let mut whitespace =
1026 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
1027 if let Some(lang_config) = self.config.languages.get(&state.language) {
1028 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
1029 state.buffer_settings.use_tabs =
1030 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
1031 state.buffer_settings.tab_size =
1032 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
1033 } else {
1034 state.buffer_settings.tab_size = self.config.editor.tab_size;
1035 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
1036 }
1037 state.buffer_settings.whitespace = whitespace;
1038 state
1039 .margins
1040 .configure_for_line_numbers(self.config.editor.line_numbers);
1041
1042 let buffer_id = BufferId(self.next_buffer_id);
1043 self.next_buffer_id += 1;
1044 self.buffers.insert(buffer_id, state);
1045 self.event_logs
1046 .insert(buffer_id, crate::model::event::EventLog::new());
1047
1048 let mut metadata =
1049 super::types::BufferMetadata::with_container_file(container_path.clone(), uri);
1050 self.notify_lsp_file_opened(&container_path, buffer_id, &mut metadata);
1055 self.buffer_metadata.insert(buffer_id, metadata);
1056
1057 let target_split = self.preferred_split_for_file();
1061 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
1062 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
1063 if let Some(view_state) = self.split_view_states.get_mut(&target_split) {
1064 view_state.add_buffer(buffer_id);
1065 let buf_state = view_state.ensure_buffer_state(buffer_id);
1066 buf_state.apply_config_defaults(
1067 self.config.editor.line_numbers,
1068 self.config.editor.highlight_current_line,
1069 line_wrap,
1070 self.config.editor.wrap_indent,
1071 wrap_column,
1072 self.config.editor.rulers.clone(),
1073 );
1074 }
1075
1076 Ok(buffer_id)
1077 }
1078}