Show project panel symlink icons for remote clients (#19464)

Kirill Bulatov created

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql  |  2 
crates/collab/migrations/20241019184824_adjust_symlink_data.sql |  2 
crates/collab/src/db/queries/projects.rs                        |  6 
crates/collab/src/db/queries/rooms.rs                           |  2 
crates/collab/src/db/tables/worktree_entry.rs                   |  2 
crates/editor/src/editor.rs                                     |  8 
crates/project_panel/src/project_panel.rs                       | 22 +-
crates/proto/proto/zed.proto                                    |  3 
crates/workspace/src/pane.rs                                    |  8 
crates/worktree/src/worktree.rs                                 | 12 
10 files changed, 31 insertions(+), 36 deletions(-)

Detailed changes

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -78,10 +78,10 @@ CREATE TABLE "worktree_entries" (
     "id" INTEGER NOT NULL,
     "is_dir" BOOL NOT NULL,
     "path" VARCHAR NOT NULL,
+    "canonical_path" TEXT,
     "inode" INTEGER NOT NULL,
     "mtime_seconds" INTEGER NOT NULL,
     "mtime_nanos" INTEGER NOT NULL,
-    "is_symlink" BOOL NOT NULL,
     "is_external" BOOL NOT NULL,
     "is_ignored" BOOL NOT NULL,
     "is_deleted" BOOL NOT NULL,

crates/collab/src/db/queries/projects.rs 🔗

@@ -317,7 +317,7 @@ impl Database {
                         inode: ActiveValue::set(entry.inode as i64),
                         mtime_seconds: ActiveValue::set(mtime.seconds as i64),
                         mtime_nanos: ActiveValue::set(mtime.nanos as i32),
-                        is_symlink: ActiveValue::set(entry.is_symlink),
+                        canonical_path: ActiveValue::set(entry.canonical_path.clone()),
                         is_ignored: ActiveValue::set(entry.is_ignored),
                         is_external: ActiveValue::set(entry.is_external),
                         git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
@@ -338,7 +338,7 @@ impl Database {
                         worktree_entry::Column::Inode,
                         worktree_entry::Column::MtimeSeconds,
                         worktree_entry::Column::MtimeNanos,
-                        worktree_entry::Column::IsSymlink,
+                        worktree_entry::Column::CanonicalPath,
                         worktree_entry::Column::IsIgnored,
                         worktree_entry::Column::GitStatus,
                         worktree_entry::Column::ScanId,
@@ -735,7 +735,7 @@ impl Database {
                             seconds: db_entry.mtime_seconds as u64,
                             nanos: db_entry.mtime_nanos as u32,
                         }),
-                        is_symlink: db_entry.is_symlink,
+                        canonical_path: db_entry.canonical_path,
                         is_ignored: db_entry.is_ignored,
                         is_external: db_entry.is_external,
                         git_status: db_entry.git_status.map(|status| status as i32),

crates/collab/src/db/queries/rooms.rs 🔗

@@ -659,7 +659,7 @@ impl Database {
                                 seconds: db_entry.mtime_seconds as u64,
                                 nanos: db_entry.mtime_nanos as u32,
                             }),
-                            is_symlink: db_entry.is_symlink,
+                            canonical_path: db_entry.canonical_path,
                             is_ignored: db_entry.is_ignored,
                             is_external: db_entry.is_external,
                             git_status: db_entry.git_status.map(|status| status as i32),

crates/collab/src/db/tables/worktree_entry.rs 🔗

@@ -16,12 +16,12 @@ pub struct Model {
     pub mtime_seconds: i64,
     pub mtime_nanos: i32,
     pub git_status: Option<i64>,
-    pub is_symlink: bool,
     pub is_ignored: bool,
     pub is_external: bool,
     pub is_deleted: bool,
     pub scan_id: i64,
     pub is_fifo: bool,
+    pub canonical_path: Option<String>,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

crates/editor/src/editor.rs 🔗

@@ -6282,11 +6282,9 @@ impl Editor {
             let project_path = buffer.read(cx).project_path(cx)?;
             let project = self.project.as_ref()?.read(cx);
             let entry = project.entry_for_path(&project_path, cx)?;
-            let abs_path = project.absolute_path(&project_path, cx)?;
-            let parent = if entry.is_symlink {
-                abs_path.canonicalize().ok()?
-            } else {
-                abs_path
+            let parent = match &entry.canonical_path {
+                Some(canonical_path) => canonical_path.to_path_buf(),
+                None => project.absolute_path(&project_path, cx)?,
             }
             .parent()?
             .to_path_buf();

crates/project_panel/src/project_panel.rs 🔗

@@ -91,7 +91,6 @@ struct EditState {
     entry_id: ProjectEntryId,
     is_new_entry: bool,
     is_dir: bool,
-    is_symlink: bool,
     depth: usize,
     processing_filename: Option<String>,
 }
@@ -987,7 +986,6 @@ impl ProjectPanel {
                 is_new_entry: true,
                 is_dir,
                 processing_filename: None,
-                is_symlink: false,
                 depth: 0,
             });
             self.filename_editor.update(cx, |editor, cx| {
@@ -1027,7 +1025,6 @@ impl ProjectPanel {
                         is_new_entry: false,
                         is_dir: entry.is_dir(),
                         processing_filename: None,
-                        is_symlink: entry.is_symlink,
                         depth: 0,
                     });
                     let file_name = entry
@@ -1533,16 +1530,15 @@ impl ProjectPanel {
 
     fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
         if let Some((worktree, entry)) = self.selected_sub_entry(cx) {
-            let abs_path = worktree.abs_path().join(&entry.path);
+            let abs_path = match &entry.canonical_path {
+                Some(canonical_path) => Some(canonical_path.to_path_buf()),
+                None => worktree.absolutize(&entry.path).ok(),
+            };
+
             let working_directory = if entry.is_dir() {
-                Some(abs_path)
+                abs_path
             } else {
-                if entry.is_symlink {
-                    abs_path.canonicalize().ok()
-                } else {
-                    Some(abs_path)
-                }
-                .and_then(|path| Some(path.parent()?.to_path_buf()))
+                abs_path.and_then(|path| Some(path.parent()?.to_path_buf()))
             };
             if let Some(working_directory) = working_directory {
                 cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone())
@@ -1830,7 +1826,6 @@ impl ProjectPanel {
                         .unwrap_or_default();
                     if let Some(edit_state) = &mut self.edit_state {
                         if edit_state.entry_id == entry.id {
-                            edit_state.is_symlink = entry.is_symlink;
                             edit_state.depth = depth;
                         }
                     }
@@ -1861,7 +1856,6 @@ impl ProjectPanel {
                         is_private: false,
                         git_status: entry.git_status,
                         canonical_path: entry.canonical_path.clone(),
-                        is_symlink: entry.is_symlink,
                         char_bag: entry.char_bag,
                         is_fifo: entry.is_fifo,
                     });
@@ -1920,7 +1914,7 @@ impl ProjectPanel {
                 let width_estimate = item_width_estimate(
                     depth,
                     path.to_string_lossy().chars().count(),
-                    entry.is_symlink,
+                    entry.canonical_path.is_some(),
                 );
 
                 match max_width_item.as_mut() {

crates/proto/proto/zed.proto 🔗

@@ -1867,12 +1867,13 @@ message Entry {
     string path = 3;
     uint64 inode = 4;
     Timestamp mtime = 5;
-    bool is_symlink = 6;
     bool is_ignored = 7;
     bool is_external = 8;
+    reserved 6;
     optional GitStatus git_status = 9;
     bool is_fifo = 10;
     optional uint64 size = 11;
+    optional string canonical_path = 12;
 }
 
 message RepositoryEntry {

crates/workspace/src/pane.rs 🔗

@@ -1739,11 +1739,9 @@ impl Pane {
             .worktree_for_entry(entry, cx)?
             .read(cx);
         let entry = worktree.entry_for_id(entry)?;
-        let abs_path = worktree.absolutize(&entry.path).ok()?;
-        if entry.is_symlink {
-            abs_path.canonicalize().ok()
-        } else {
-            Some(abs_path)
+        match &entry.canonical_path {
+            Some(canonical_path) => Some(canonical_path.to_path_buf()),
+            None => worktree.absolutize(&entry.path).ok(),
         }
     }
 

crates/worktree/src/worktree.rs 🔗

@@ -3203,7 +3203,6 @@ pub struct Entry {
     pub mtime: Option<SystemTime>,
 
     pub canonical_path: Option<Box<Path>>,
-    pub is_symlink: bool,
     /// Whether this entry is ignored by Git.
     ///
     /// We only scan ignored entries once the directory is expanded and
@@ -3280,7 +3279,6 @@ impl Entry {
             mtime: Some(metadata.mtime),
             size: metadata.len,
             canonical_path,
-            is_symlink: metadata.is_symlink,
             is_ignored: false,
             is_external: false,
             is_private: false,
@@ -5249,12 +5247,15 @@ impl<'a> From<&'a Entry> for proto::Entry {
             path: entry.path.to_string_lossy().into(),
             inode: entry.inode,
             mtime: entry.mtime.map(|time| time.into()),
-            is_symlink: entry.is_symlink,
             is_ignored: entry.is_ignored,
             is_external: entry.is_external,
             git_status: entry.git_status.map(git_status_to_proto),
             is_fifo: entry.is_fifo,
             size: Some(entry.size),
+            canonical_path: entry
+                .canonical_path
+                .as_ref()
+                .map(|path| path.to_string_lossy().to_string()),
         }
     }
 }
@@ -5277,12 +5278,13 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
             inode: entry.inode,
             mtime: entry.mtime.map(|time| time.into()),
             size: entry.size.unwrap_or(0),
-            canonical_path: None,
+            canonical_path: entry
+                .canonical_path
+                .map(|path_string| Box::from(Path::new(&path_string))),
             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,
             char_bag,
             is_fifo: entry.is_fifo,
         })