diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7676d0f904..dd64447b97 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -29,7 +29,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem, Tooltip}; use unicase::UniCase; use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt}; use workspace::{ @@ -103,6 +103,7 @@ pub struct EntryDetails { is_cut: bool, git_status: Option, is_private: bool, + canonical_path: Option, } #[derive(PartialEq, Clone, Default, Debug, Deserialize)] @@ -1444,11 +1445,12 @@ impl ProjectPanel { path: entry.path.join("\0").into(), inode: 0, mtime: entry.mtime, - is_symlink: false, is_ignored: entry.is_ignored, is_external: false, is_private: false, git_status: entry.git_status, + canonical_path: entry.canonical_path.clone(), + is_symlink: entry.is_symlink, }); } if expanded_dir_ids.binary_search(&entry.id).is_err() @@ -1646,6 +1648,7 @@ impl ProjectPanel { .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id), git_status: status, is_private: entry.is_private, + canonical_path: entry.canonical_path.clone(), }; if let Some(edit_state) = &self.edit_state { @@ -1738,6 +1741,12 @@ impl ProjectPanel { icon = FileIcons::get_icon(Path::new(&filename), cx); } } + + let canonical_path = details + .canonical_path + .as_ref() + .map(|f| f.to_string_lossy().to_string()); + let depth = details.depth; div() .id(entry_id.to_proto() as usize) @@ -1759,6 +1768,14 @@ impl ProjectPanel { .indent_level(depth) .indent_step_size(px(settings.indent_size)) .selected(is_selected) + .when_some(canonical_path, |this, path| { + this.end_slot::( + Icon::new(IconName::ArrowUpRight) + .size(IconSize::Indicator) + .color(filename_text_color), + ) + .tooltip(move |cx| Tooltip::text(format!("{path} • Symbolic Link"), cx)) + }) .child(if let Some(icon) = &icon { h_flex().child(Icon::from_path(icon.to_string()).color(filename_text_color)) } else { diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index ccef0ab290..48384e6026 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -471,6 +471,7 @@ impl Worktree { &metadata, &next_entry_id, snapshot.root_char_bag, + None ), fs.as_ref(), ); @@ -3060,8 +3061,9 @@ pub struct Entry { pub path: Arc, pub inode: u64, pub mtime: Option, - pub is_symlink: bool, + pub canonical_path: Option, + pub is_symlink: bool, /// Whether this entry is ignored by Git. /// /// We only scan ignored entries once the directory is expanded and @@ -3119,6 +3121,7 @@ impl Entry { metadata: &fs::Metadata, next_entry_id: &AtomicUsize, root_char_bag: CharBag, + canonical_path: Option, ) -> Self { Self { id: ProjectEntryId::new(next_entry_id), @@ -3130,6 +3133,7 @@ impl Entry { path, inode: metadata.inode, mtime: Some(metadata.mtime), + canonical_path, is_symlink: metadata.is_symlink, is_ignored: false, is_external: false, @@ -3861,6 +3865,7 @@ impl BackgroundScanner { &child_metadata, &next_entry_id, root_char_bag, + None, ); if job.is_external { @@ -3894,6 +3899,8 @@ impl BackgroundScanner { if !canonical_path.starts_with(root_canonical_path) { child_entry.is_external = true; } + + child_entry.canonical_path = Some(canonical_path); } if child_entry.is_dir() { @@ -4049,7 +4056,13 @@ impl BackgroundScanner { metadata, self.next_entry_id.as_ref(), state.snapshot.root_char_bag, + if metadata.is_symlink { + Some(canonical_path.to_path_buf()) + } else { + None + }, ); + let is_dir = fs_entry.is_dir(); fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir); fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path); @@ -5048,11 +5061,12 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { path, inode: entry.inode, mtime: entry.mtime.map(|time| time.into()), - is_symlink: entry.is_symlink, + canonical_path: None, is_ignored: entry.is_ignored, is_external: entry.is_external, git_status: git_status_from_proto(entry.git_status), is_private: false, + is_symlink: entry.is_symlink, }) } }