workspace: Fix incorrect project paths on welcome page (#54056)

Bennet Bo Fenner created

Fixes an issue where we would only display the first path of the project
on the welcome page

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed an issue where incorrect project paths would be displayed on the
welcome tab

Change summary

crates/workspace/src/welcome.rs | 60 ++++++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 11 deletions(-)

Detailed changes

crates/workspace/src/welcome.rs 🔗

@@ -399,18 +399,11 @@ impl WelcomePage {
         location: &SerializedWorkspaceLocation,
         paths: &PathList,
     ) -> impl IntoElement {
+        let name = project_name(paths);
+
         let (icon, title) = match location {
-            SerializedWorkspaceLocation::Local => {
-                let path = paths.paths().first().map(|p| p.as_path());
-                let name = path
-                    .and_then(|p| p.file_name())
-                    .map(|n| n.to_string_lossy().to_string())
-                    .unwrap_or_else(|| "Untitled".to_string());
-                (IconName::Folder, name)
-            }
-            SerializedWorkspaceLocation::Remote(_) => {
-                (IconName::Server, "Remote Project".to_string())
-            }
+            SerializedWorkspaceLocation::Local => (IconName::Folder, name),
+            SerializedWorkspaceLocation::Remote(_) => (IconName::Server, name),
         };
 
         SectionButton::new(
@@ -661,3 +654,48 @@ mod persistence {
         }
     }
 }
+
+fn project_name(paths: &PathList) -> String {
+    let joined = paths
+        .paths()
+        .iter()
+        .filter_map(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
+        .collect::<Vec<_>>()
+        .join(", ");
+    if joined.is_empty() {
+        "Untitled".to_string()
+    } else {
+        joined
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_project_name_empty() {
+        let paths = PathList::new::<&str>(&[]);
+        assert_eq!(project_name(&paths), "Untitled");
+    }
+
+    #[test]
+    fn test_project_name_single() {
+        let paths = PathList::new(&["/home/user/my-project"]);
+        assert_eq!(project_name(&paths), "my-project");
+    }
+
+    #[test]
+    fn test_project_name_multiple() {
+        // PathList sorts lexicographically, so filenames appear in alpha order
+        let paths = PathList::new(&["/home/user/zed", "/home/user/api"]);
+        assert_eq!(project_name(&paths), "api, zed");
+    }
+
+    #[test]
+    fn test_project_name_root_path_filtered() {
+        // A bare root "/" has no file_name(), falls back to "Untitled"
+        let paths = PathList::new(&["/"]);
+        assert_eq!(project_name(&paths), "Untitled");
+    }
+}