1use anyhow::Result as AnyhowResult;
2use rust_i18n::t;
3
4use super::*;
5use crate::view::file_tree::TreeNode;
6use std::path::PathBuf;
7
8fn get_parent_dir_path(node: &TreeNode) -> PathBuf {
11 if node.is_dir() {
12 node.entry.path.clone()
13 } else {
14 node.entry
15 .path
16 .parent()
17 .map(|p| p.to_path_buf())
18 .unwrap_or_else(|| node.entry.path.clone())
19 }
20}
21
22fn timestamp_suffix() -> u64 {
24 std::time::SystemTime::now()
25 .duration_since(std::time::UNIX_EPOCH)
26 .unwrap()
27 .as_secs()
28}
29
30fn get_parent_node_id(
33 tree: &crate::view::file_tree::FileTree,
34 selected_id: crate::view::file_tree::NodeId,
35 node_is_dir: bool,
36) -> crate::view::file_tree::NodeId {
37 if node_is_dir {
38 selected_id
39 } else {
40 tree.get_node(selected_id)
41 .and_then(|n| n.parent)
42 .unwrap_or(selected_id)
43 }
44}
45
46impl Editor {
47 pub fn file_explorer_visible(&self) -> bool {
48 self.file_explorer_visible
49 }
50
51 pub fn file_explorer(&self) -> Option<&FileTreeView> {
52 self.file_explorer.as_ref()
53 }
54
55 pub fn toggle_file_explorer(&mut self) {
56 self.file_explorer_visible = !self.file_explorer_visible;
57
58 if self.file_explorer_visible {
59 if self.file_explorer.is_none() {
60 self.init_file_explorer();
61 }
62 self.key_context = KeyContext::FileExplorer;
63 self.set_status_message(t!("explorer.opened").to_string());
64 self.sync_file_explorer_to_active_file();
65 } else {
66 self.key_context = KeyContext::Normal;
67 self.set_status_message(t!("explorer.closed").to_string());
68 }
69 }
70
71 pub fn show_file_explorer(&mut self) {
72 if !self.file_explorer_visible {
73 self.toggle_file_explorer();
74 }
75 }
76
77 pub fn sync_file_explorer_to_active_file(&mut self) {
78 if !self.file_explorer_visible {
79 return;
80 }
81
82 if self.file_explorer_sync_in_progress {
84 return;
85 }
86
87 if let Some(metadata) = self.buffer_metadata.get(&self.active_buffer()) {
88 if let Some(file_path) = metadata.file_path() {
89 let target_path = file_path.clone();
90 let working_dir = self.working_dir.clone();
91
92 if target_path.starts_with(&working_dir) {
93 if let Some(mut view) = self.file_explorer.take() {
94 tracing::trace!(
95 "sync_file_explorer_to_active_file: taking file_explorer for async expand to {:?}",
96 target_path
97 );
98 if let (Some(runtime), Some(bridge)) =
99 (&self.tokio_runtime, &self.async_bridge)
100 {
101 let sender = bridge.sender();
102 self.file_explorer_sync_in_progress = true;
104
105 runtime.spawn(async move {
106 let _success = view.expand_and_select_file(&target_path).await;
107 let _ = sender.send(AsyncMessage::FileExplorerExpandedToPath(view));
108 });
109 } else {
110 self.file_explorer = Some(view);
111 }
112 }
113 }
114 }
115 }
116 }
117
118 pub fn focus_file_explorer(&mut self) {
119 if self.file_explorer_visible {
120 self.on_editor_focus_lost();
122
123 self.cancel_search_prompt_if_active();
125
126 self.key_context = KeyContext::FileExplorer;
127 self.set_status_message(t!("explorer.focused").to_string());
128 self.sync_file_explorer_to_active_file();
129 } else {
130 self.toggle_file_explorer();
131 }
132 }
133
134 pub fn focus_editor(&mut self) {
135 self.key_context = KeyContext::Normal;
136 self.set_status_message(t!("editor.focused").to_string());
137 }
138
139 pub(crate) fn init_file_explorer(&mut self) {
140 let root_path = if self.filesystem.remote_connection_info().is_some() {
142 match self.filesystem.home_dir() {
143 Ok(home) => home,
144 Err(e) => {
145 tracing::error!("Failed to get remote home directory: {}", e);
146 self.set_status_message(format!("Failed to get remote home: {}", e));
147 return;
148 }
149 }
150 } else {
151 self.working_dir.clone()
152 };
153
154 if let (Some(runtime), Some(bridge)) = (&self.tokio_runtime, &self.async_bridge) {
155 let fs_manager = Arc::clone(&self.fs_manager);
156 let sender = bridge.sender();
157
158 runtime.spawn(async move {
159 match FileTree::new(root_path, fs_manager).await {
160 Ok(mut tree) => {
161 let root_id = tree.root_id();
162 if let Err(e) = tree.expand_node(root_id).await {
163 tracing::warn!("Failed to expand root directory: {}", e);
164 }
165
166 let view = FileTreeView::new(tree);
167 let _ = sender.send(AsyncMessage::FileExplorerInitialized(view));
168 }
169 Err(e) => {
170 tracing::error!("Failed to initialize file explorer: {}", e);
171 }
172 }
173 });
174
175 self.set_status_message(t!("explorer.initializing").to_string());
176 }
177 }
178
179 pub fn file_explorer_navigate_up(&mut self) {
180 if let Some(explorer) = &mut self.file_explorer {
181 explorer.select_prev();
182 explorer.update_scroll_for_selection();
183 }
184 }
185
186 pub fn file_explorer_navigate_down(&mut self) {
187 if let Some(explorer) = &mut self.file_explorer {
188 explorer.select_next();
189 explorer.update_scroll_for_selection();
190 }
191 }
192
193 pub fn file_explorer_page_up(&mut self) {
194 if let Some(explorer) = &mut self.file_explorer {
195 explorer.select_page_up();
196 explorer.update_scroll_for_selection();
197 }
198 }
199
200 pub fn file_explorer_page_down(&mut self) {
201 if let Some(explorer) = &mut self.file_explorer {
202 explorer.select_page_down();
203 explorer.update_scroll_for_selection();
204 }
205 }
206
207 pub fn file_explorer_collapse(&mut self) {
211 let Some(explorer) = &self.file_explorer else {
212 return;
213 };
214
215 let Some(selected_id) = explorer.get_selected() else {
216 return;
217 };
218
219 let Some(node) = explorer.tree().get_node(selected_id) else {
220 return;
221 };
222
223 if node.is_dir() && node.is_expanded() {
225 self.file_explorer_toggle_expand();
226 return;
227 }
228
229 if let Some(explorer) = &mut self.file_explorer {
231 explorer.select_parent();
232 explorer.update_scroll_for_selection();
233 }
234 }
235
236 pub fn file_explorer_toggle_expand(&mut self) {
237 let selected_id = if let Some(explorer) = &self.file_explorer {
238 explorer.get_selected()
239 } else {
240 return;
241 };
242
243 let Some(selected_id) = selected_id else {
244 return;
245 };
246
247 let (is_dir, is_expanded, name) = if let Some(explorer) = &self.file_explorer {
248 let node = explorer.tree().get_node(selected_id);
249 if let Some(node) = node {
250 (node.is_dir(), node.is_expanded(), node.entry.name.clone())
251 } else {
252 return;
253 }
254 } else {
255 return;
256 };
257
258 if !is_dir {
259 return;
260 }
261
262 let status_msg = if is_expanded {
263 t!("explorer.collapsing").to_string()
264 } else {
265 t!("explorer.loading_dir", name = &name).to_string()
266 };
267 self.set_status_message(status_msg);
268
269 if let (Some(runtime), Some(explorer)) = (&self.tokio_runtime, &mut self.file_explorer) {
270 let tree = explorer.tree_mut();
271 let result = runtime.block_on(tree.toggle_node(selected_id));
272
273 let final_name = explorer
274 .tree()
275 .get_node(selected_id)
276 .map(|n| n.entry.name.clone());
277 let final_expanded = explorer
278 .tree()
279 .get_node(selected_id)
280 .map(|n| n.is_expanded())
281 .unwrap_or(false);
282
283 match result {
284 Ok(()) => {
285 if final_expanded {
286 let dir_path = explorer
287 .tree()
288 .get_node(selected_id)
289 .map(|n| n.entry.path.clone());
290
291 if let Some(dir_path) = dir_path {
292 if let Err(e) = explorer.load_gitignore_for_dir(&dir_path) {
293 tracing::warn!(
294 "Failed to load .gitignore from {:?}: {}",
295 dir_path,
296 e
297 );
298 }
299 }
300 }
301
302 if let Some(name) = final_name {
303 let msg = if final_expanded {
304 t!("explorer.expanded", name = &name).to_string()
305 } else {
306 t!("explorer.collapsed", name = &name).to_string()
307 };
308 self.set_status_message(msg);
309 }
310 }
311 Err(e) => {
312 self.set_status_message(
313 t!("explorer.error", error = e.to_string()).to_string(),
314 );
315 }
316 }
317 }
318 }
319
320 pub fn file_explorer_open_file(&mut self) -> AnyhowResult<()> {
321 let entry_type = self
322 .file_explorer
323 .as_ref()
324 .and_then(|explorer| explorer.get_selected_entry())
325 .map(|entry| (entry.is_dir(), entry.path.clone(), entry.name.clone()));
326
327 if let Some((is_dir, path, name)) = entry_type {
328 if is_dir {
329 self.file_explorer_toggle_expand();
330 } else {
331 tracing::info!("[SYNTAX DEBUG] file_explorer opening file: {:?}", path);
332 self.open_file(&path)?;
333 self.set_status_message(t!("explorer.opened_file", name = &name).to_string());
334 self.focus_editor();
335 }
336 }
337 Ok(())
338 }
339
340 pub fn file_explorer_refresh(&mut self) {
341 let (selected_id, node_name) = if let Some(explorer) = &self.file_explorer {
342 if let Some(selected_id) = explorer.get_selected() {
343 let node_name = explorer
344 .tree()
345 .get_node(selected_id)
346 .map(|n| n.entry.name.clone());
347 (Some(selected_id), node_name)
348 } else {
349 (None, None)
350 }
351 } else {
352 return;
353 };
354
355 let Some(selected_id) = selected_id else {
356 return;
357 };
358
359 if let Some(name) = &node_name {
360 self.set_status_message(t!("explorer.refreshing", name = name).to_string());
361 }
362
363 if let (Some(runtime), Some(explorer)) = (&self.tokio_runtime, &mut self.file_explorer) {
364 let tree = explorer.tree_mut();
365 let result = runtime.block_on(tree.refresh_node(selected_id));
366 match result {
367 Ok(()) => {
368 if let Some(name) = node_name {
369 self.set_status_message(t!("explorer.refreshed", name = &name).to_string());
370 } else {
371 self.set_status_message(t!("explorer.refreshed_default").to_string());
372 }
373 }
374 Err(e) => {
375 self.set_status_message(
376 t!("explorer.error_refreshing", error = e.to_string()).to_string(),
377 );
378 }
379 }
380 }
381 }
382
383 pub fn file_explorer_new_file(&mut self) {
384 if let Some(explorer) = &mut self.file_explorer {
385 if let Some(selected_id) = explorer.get_selected() {
386 let node = explorer.tree().get_node(selected_id);
387 if let Some(node) = node {
388 let parent_path = get_parent_dir_path(node);
389 let filename = format!("untitled_{}.txt", timestamp_suffix());
390 let file_path = parent_path.join(&filename);
391
392 if let Some(runtime) = &self.tokio_runtime {
393 let path_clone = file_path.clone();
394 let result = self.filesystem.create_file(&path_clone).map(|_| ());
395
396 match result {
397 Ok(_) => {
398 let parent_id =
399 get_parent_node_id(explorer.tree(), selected_id, node.is_dir());
400 let tree = explorer.tree_mut();
401 let _ = runtime.block_on(tree.refresh_node(parent_id));
402 self.set_status_message(
403 t!("explorer.created_file", name = &filename).to_string(),
404 );
405
406 let _ = self.open_file(&path_clone);
408
409 let prompt = crate::view::prompt::Prompt::new(
412 t!("explorer.rename_prompt").to_string(),
413 crate::view::prompt::PromptType::FileExplorerRename {
414 original_path: path_clone,
415 original_name: filename.clone(),
416 is_new_file: true,
417 },
418 );
419 self.prompt = Some(prompt);
420 }
421 Err(e) => {
422 self.set_status_message(
423 t!("explorer.error_creating_file", error = e.to_string())
424 .to_string(),
425 );
426 }
427 }
428 }
429 }
430 }
431 }
432 }
433
434 pub fn file_explorer_new_directory(&mut self) {
435 if let Some(explorer) = &mut self.file_explorer {
436 if let Some(selected_id) = explorer.get_selected() {
437 let node = explorer.tree().get_node(selected_id);
438 if let Some(node) = node {
439 let parent_path = get_parent_dir_path(node);
440 let dirname = format!("New Folder {}", timestamp_suffix());
441 let dir_path = parent_path.join(&dirname);
442
443 if let Some(runtime) = &self.tokio_runtime {
444 let path_clone = dir_path.clone();
445 let dirname_clone = dirname.clone();
446 let result = self.filesystem.create_dir(&path_clone);
447
448 match result {
449 Ok(_) => {
450 let parent_id =
451 get_parent_node_id(explorer.tree(), selected_id, node.is_dir());
452 let tree = explorer.tree_mut();
453 let _ = runtime.block_on(tree.refresh_node(parent_id));
454 self.set_status_message(
455 t!("explorer.created_dir", name = &dirname_clone).to_string(),
456 );
457
458 let prompt = crate::view::prompt::Prompt::with_initial_text(
460 t!("explorer.rename_prompt").to_string(),
461 crate::view::prompt::PromptType::FileExplorerRename {
462 original_path: path_clone,
463 original_name: dirname_clone,
464 is_new_file: true,
465 },
466 dirname,
467 );
468 self.prompt = Some(prompt);
469 }
470 Err(e) => {
471 self.set_status_message(
472 t!("explorer.error_creating_dir", error = e.to_string())
473 .to_string(),
474 );
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482
483 pub fn file_explorer_delete(&mut self) {
484 if let Some(explorer) = &self.file_explorer {
485 if let Some(selected_id) = explorer.get_selected() {
486 if selected_id == explorer.tree().root_id() {
488 self.set_status_message(t!("explorer.cannot_delete_root").to_string());
489 return;
490 }
491
492 let node = explorer.tree().get_node(selected_id);
493 if let Some(node) = node {
494 let path = node.entry.path.clone();
495 let name = node.entry.name.clone();
496 let is_dir = node.is_dir();
497
498 let type_str = if is_dir { "directory" } else { "file" };
499 self.start_prompt(
500 t!("explorer.delete_confirm", "type" = type_str, name = &name).to_string(),
501 PromptType::ConfirmDeleteFile { path, is_dir },
502 );
503 }
504 }
505 }
506 }
507
508 pub fn perform_file_explorer_delete(&mut self, path: std::path::PathBuf, _is_dir: bool) {
512 let name = path
513 .file_name()
514 .map(|n| n.to_string_lossy().to_string())
515 .unwrap_or_default();
516
517 let delete_result = if self.filesystem.remote_connection_info().is_some() {
520 self.move_to_remote_trash(&path)
521 } else {
522 trash::delete(&path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
523 };
524
525 match delete_result {
526 Ok(_) => {
527 if let Some(explorer) = &mut self.file_explorer {
529 if let Some(runtime) = &self.tokio_runtime {
530 if let Some(node) = explorer.tree().get_node_by_path(&path) {
532 let node_id = node.id;
533 let parent_id = get_parent_node_id(explorer.tree(), node_id, false);
534
535 let deleted_index = explorer.get_selected_index();
537
538 let _ = runtime.block_on(explorer.tree_mut().refresh_node(parent_id));
539
540 let visible = explorer.tree().get_visible_nodes();
543 if !visible.is_empty() {
544 let new_index = if let Some(idx) = deleted_index {
545 idx.min(visible.len().saturating_sub(1))
546 } else {
547 0
548 };
549 explorer.set_selected(Some(visible[new_index]));
550 } else {
551 explorer.set_selected(Some(parent_id));
553 }
554 }
555 }
556 }
557 self.set_status_message(t!("explorer.moved_to_trash", name = &name).to_string());
558
559 self.key_context = KeyContext::FileExplorer;
561 }
562 Err(e) => {
563 self.set_status_message(
564 t!("explorer.error_trash", error = e.to_string()).to_string(),
565 );
566 }
567 }
568 }
569
570 fn move_to_remote_trash(&self, path: &std::path::Path) -> std::io::Result<()> {
572 let home = self.filesystem.home_dir()?;
574 let trash_dir = home.join(".local/share/fresh/trash");
575
576 if !self.filesystem.exists(&trash_dir) {
578 self.filesystem.create_dir_all(&trash_dir)?;
579 }
580
581 let file_name = path
583 .file_name()
584 .unwrap_or_else(|| std::ffi::OsStr::new("unnamed"));
585 let timestamp = std::time::SystemTime::now()
586 .duration_since(std::time::UNIX_EPOCH)
587 .map(|d| d.as_secs())
588 .unwrap_or(0);
589 let trash_name = format!("{}.{}", file_name.to_string_lossy(), timestamp);
590 let trash_path = trash_dir.join(trash_name);
591
592 self.filesystem.rename(path, &trash_path)
594 }
595
596 pub fn file_explorer_rename(&mut self) {
597 if let Some(explorer) = &self.file_explorer {
598 if let Some(selected_id) = explorer.get_selected() {
599 if selected_id == explorer.tree().root_id() {
601 self.set_status_message(t!("explorer.cannot_rename_root").to_string());
602 return;
603 }
604
605 let node = explorer.tree().get_node(selected_id);
606 if let Some(node) = node {
607 let old_path = node.entry.path.clone();
608 let old_name = node.entry.name.clone();
609
610 let prompt = crate::view::prompt::Prompt::with_initial_text(
612 t!("explorer.rename_prompt").to_string(),
613 crate::view::prompt::PromptType::FileExplorerRename {
614 original_path: old_path,
615 original_name: old_name.clone(),
616 is_new_file: false,
617 },
618 old_name,
619 );
620 self.prompt = Some(prompt);
621 }
622 }
623 }
624 }
625
626 pub fn perform_file_explorer_rename(
628 &mut self,
629 original_path: std::path::PathBuf,
630 original_name: String,
631 new_name: String,
632 is_new_file: bool,
633 ) {
634 if new_name.is_empty() || new_name == original_name {
635 self.set_status_message(t!("explorer.rename_cancelled").to_string());
636 return;
637 }
638
639 let new_path = original_path
640 .parent()
641 .map(|p| p.join(&new_name))
642 .unwrap_or_else(|| original_path.clone());
643
644 if let Some(runtime) = &self.tokio_runtime {
645 let result = self.filesystem.rename(&original_path, &new_path);
646
647 match result {
648 Ok(_) => {
649 if let Some(explorer) = &mut self.file_explorer {
651 if let Some(selected_id) = explorer.get_selected() {
652 let parent_id = get_parent_node_id(explorer.tree(), selected_id, false);
653 let tree = explorer.tree_mut();
654 let _ = runtime.block_on(tree.refresh_node(parent_id));
655 }
656 explorer.navigate_to_path(&new_path);
658 }
659
660 let buffer_to_update = self
662 .buffers
663 .iter()
664 .find(|(_, state)| state.buffer.file_path() == Some(&original_path))
665 .map(|(id, _)| *id);
666
667 if let Some(buffer_id) = buffer_to_update {
668 if let Some(state) = self.buffers.get_mut(&buffer_id) {
670 state.buffer.set_file_path(new_path.clone());
671 }
672
673 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
675 let file_uri = url::Url::from_file_path(&new_path)
677 .ok()
678 .and_then(|u| u.as_str().parse::<lsp_types::Uri>().ok());
679
680 metadata.kind = super::BufferKind::File {
682 path: new_path.clone(),
683 uri: file_uri,
684 };
685
686 metadata.display_name = super::BufferMetadata::display_name_for_path(
688 &new_path,
689 &self.working_dir,
690 );
691 }
692
693 if is_new_file {
696 self.key_context = KeyContext::Normal;
697 }
698 }
699
700 self.set_status_message(
701 t!("explorer.renamed", old = &original_name, new = &new_name).to_string(),
702 );
703 }
704 Err(e) => {
705 self.set_status_message(
706 t!("explorer.error_renaming", error = e.to_string()).to_string(),
707 );
708 }
709 }
710 }
711 }
712
713 pub fn file_explorer_toggle_hidden(&mut self) {
714 if let Some(explorer) = &mut self.file_explorer {
715 explorer.toggle_show_hidden();
716 let msg = if explorer.ignore_patterns().show_hidden() {
717 t!("explorer.showing_hidden")
718 } else {
719 t!("explorer.hiding_hidden")
720 };
721 self.set_status_message(msg.to_string());
722 }
723 }
724
725 pub fn file_explorer_toggle_gitignored(&mut self) {
726 if let Some(explorer) = &mut self.file_explorer {
727 explorer.toggle_show_gitignored();
728 let show = explorer.ignore_patterns().show_gitignored();
729 let msg = if show {
730 t!("explorer.showing_gitignored")
731 } else {
732 t!("explorer.hiding_gitignored")
733 };
734 self.set_status_message(msg.to_string());
735 }
736 }
737
738 pub fn handle_set_file_explorer_decorations(
739 &mut self,
740 namespace: String,
741 decorations: Vec<crate::view::file_tree::FileExplorerDecoration>,
742 ) {
743 let normalized: Vec<crate::view::file_tree::FileExplorerDecoration> = decorations
744 .into_iter()
745 .filter_map(|mut decoration| {
746 let path = if decoration.path.is_absolute() {
747 decoration.path
748 } else {
749 self.working_dir.join(&decoration.path)
750 };
751 let path = normalize_path(&path);
752 if path.starts_with(&self.working_dir) {
753 decoration.path = path;
754 Some(decoration)
755 } else {
756 None
757 }
758 })
759 .collect();
760
761 self.file_explorer_decorations.insert(namespace, normalized);
762 self.rebuild_file_explorer_decoration_cache();
763 }
764
765 pub fn handle_clear_file_explorer_decorations(&mut self, namespace: &str) {
766 self.file_explorer_decorations.remove(namespace);
767 self.rebuild_file_explorer_decoration_cache();
768 }
769
770 fn rebuild_file_explorer_decoration_cache(&mut self) {
771 let decorations = self
772 .file_explorer_decorations
773 .values()
774 .flat_map(|entries| entries.iter().cloned());
775 self.file_explorer_decoration_cache =
776 crate::view::file_tree::FileExplorerDecorationCache::rebuild(
777 decorations,
778 &self.working_dir,
779 );
780 }
781}