Fix Recent Documents List (continues #8952) (#9919)

Daniel Zhu created

@SomeoneToIgnore This code should 100% work for future Zed users, but
for current Zed users, Zed's internal list of recents may not be synced
w/ macOS' Recent Documents at first. If needed this can be fixed by
calling `cx.refresh_recent_documents` on startup, but that feels a bit
unnecessary.

Release Notes:

- Fixes behavior of Recent Documents list on macOS

Change summary

Cargo.lock                                    |  2 
crates/gpui/src/app.rs                        | 11 +---
crates/gpui/src/platform.rs                   |  3 
crates/gpui/src/platform/mac/platform.rs      | 16 ------
crates/gpui/src/platform/test/platform.rs     |  6 -
crates/project/src/project.rs                 |  6 ++
crates/recent_projects/Cargo.toml             |  2 
crates/recent_projects/src/recent_projects.rs | 15 -------
crates/workspace/src/workspace.rs             | 45 --------------------
9 files changed, 17 insertions(+), 89 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7648,11 +7648,9 @@ dependencies = [
 name = "recent_projects"
 version = "0.1.0"
 dependencies = [
- "collections",
  "editor",
  "fuzzy",
  "gpui",
- "itertools 0.11.0",
  "language",
  "menu",
  "ordered-float 2.10.0",

crates/gpui/src/app.rs 🔗

@@ -1129,17 +1129,14 @@ impl AppContext {
         self.platform.set_menus(menus, &self.keymap.borrow());
     }
 
-    /// Adds given path to list of recent paths for the application.
+    /// Adds given path to the bottom of the list of recent paths for the application.
     /// The list is usually shown on the application icon's context menu in the dock,
     /// and allows to open the recent files via that context menu.
-    pub fn add_recent_documents(&mut self, paths: &[PathBuf]) {
-        self.platform.add_recent_documents(paths);
+    /// If the path is already in the list, it will be moved to the bottom of the list.
+    pub fn add_recent_document(&mut self, path: &Path) {
+        self.platform.add_recent_document(path);
     }
 
-    /// Clears the list of recent paths from the application.
-    pub fn clear_recent_documents(&mut self) {
-        self.platform.clear_recent_documents();
-    }
     /// Dispatch an action to the currently active window or global action handler
     /// See [action::Action] for more information on how actions work
     pub fn dispatch_action(&mut self, action: &dyn Action) {

crates/gpui/src/platform.rs 🔗

@@ -118,8 +118,7 @@ pub(crate) trait Platform: 'static {
     fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
 
     fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
-    fn add_recent_documents(&self, _paths: &[PathBuf]) {}
-    fn clear_recent_documents(&self) {}
+    fn add_recent_document(&self, _path: &Path) {}
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
     fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
     fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -764,12 +764,8 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn add_recent_documents(&self, paths: &[PathBuf]) {
-        for path in paths {
-            let Some(path_str) = path.to_str() else {
-                log::error!("Not adding to recent documents a non-unicode path: {path:?}");
-                continue;
-            };
+    fn add_recent_document(&self, path: &Path) {
+        if let Some(path_str) = path.to_str() {
             unsafe {
                 let document_controller: id =
                     msg_send![class!(NSDocumentController), sharedDocumentController];
@@ -779,14 +775,6 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn clear_recent_documents(&self) {
-        unsafe {
-            let document_controller: id =
-                msg_send![class!(NSDocumentController), sharedDocumentController];
-            let _: () = msg_send![document_controller, clearRecentDocuments:nil];
-        }
-    }
-
     fn local_timezone(&self) -> UtcOffset {
         unsafe {
             let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];

crates/gpui/src/platform/test/platform.rs 🔗

@@ -9,7 +9,7 @@ use futures::channel::oneshot;
 use parking_lot::Mutex;
 use std::{
     cell::RefCell,
-    path::PathBuf,
+    path::{Path, PathBuf},
     rc::{Rc, Weak},
     sync::Arc,
 };
@@ -239,9 +239,7 @@ impl Platform for TestPlatform {
 
     fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
 
-    fn add_recent_documents(&self, _paths: &[PathBuf]) {}
-
-    fn clear_recent_documents(&self) {}
+    fn add_recent_document(&self, _paths: &Path) {}
 
     fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
 

crates/project/src/project.rs 🔗

@@ -6749,6 +6749,12 @@ impl Project {
                         let worktree = worktree?;
                         project
                             .update(&mut cx, |project, cx| project.add_worktree(&worktree, cx))?;
+
+                        cx.update(|cx| {
+                            cx.add_recent_document(&path);
+                        })
+                        .log_err();
+
                         Ok(worktree)
                     }
                     .map_err(Arc::new)

crates/recent_projects/Cargo.toml 🔗

@@ -13,10 +13,8 @@ path = "src/recent_projects.rs"
 doctest = false
 
 [dependencies]
-collections.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
-itertools.workspace = true
 menu.workspace = true
 ordered-float.workspace = true
 picker.workspace = true

crates/recent_projects/src/recent_projects.rs 🔗

@@ -1,10 +1,8 @@
-use collections::HashMap;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Result,
     Subscription, Task, View, ViewContext, WeakView,
 };
-use itertools::Itertools;
 use ordered_float::OrderedFloat;
 use picker::{
     highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
@@ -431,20 +429,7 @@ impl RecentProjectsDelegate {
                     .recent_workspaces_on_disk()
                     .await
                     .unwrap_or_default();
-                let mut unique_added_paths = HashMap::default();
-                for (id, workspace) in &workspaces {
-                    for path in workspace.paths().iter() {
-                        unique_added_paths.insert(path.clone(), id);
-                    }
-                }
-                let updated_paths = unique_added_paths
-                    .into_iter()
-                    .sorted_by_key(|(_, id)| *id)
-                    .map(|(path, _)| path)
-                    .collect::<Vec<_>>();
                 this.update(&mut cx, move |picker, cx| {
-                    cx.clear_recent_documents();
-                    cx.add_recent_documents(&updated_paths);
                     picker.delegate.workspaces = workspaces;
                     picker.delegate.set_selected_index(ix - 1, cx);
                     picker.delegate.reset_selected_match_index = false;

crates/workspace/src/workspace.rs 🔗

@@ -607,16 +607,7 @@ impl Workspace {
 
                 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
                     this.update_window_title(cx);
-                    let workspace_serialization = this.serialize_workspace(cx);
-                    cx.spawn(|workspace, mut cx| async move {
-                        workspace_serialization.await;
-                        workspace
-                            .update(&mut cx, |workspace, cx| {
-                                workspace.refresh_recent_documents(cx)
-                            })?
-                            .await
-                    })
-                    .detach_and_log_err(cx)
+                    this.serialize_workspace(cx).detach();
                 }
 
                 project::Event::DisconnectedFromHost => {
@@ -946,12 +937,7 @@ impl Workspace {
                 .unwrap_or_default();
 
             window
-                .update(&mut cx, |workspace, cx| {
-                    workspace
-                        .refresh_recent_documents(cx)
-                        .detach_and_log_err(cx);
-                    cx.activate_window()
-                })
+                .update(&mut cx, |_, cx| cx.activate_window())
                 .log_err();
             Ok((window, opened_items))
         })
@@ -3491,33 +3477,6 @@ impl Workspace {
         Task::ready(())
     }
 
-    fn refresh_recent_documents(&self, cx: &mut AppContext) -> Task<Result<()>> {
-        if !self.project.read(cx).is_local() {
-            return Task::ready(Ok(()));
-        }
-        cx.spawn(|cx| async move {
-            let recents = WORKSPACE_DB
-                .recent_workspaces_on_disk()
-                .await
-                .unwrap_or_default();
-            let mut unique_paths = HashMap::default();
-            for (id, workspace) in &recents {
-                for path in workspace.paths().iter() {
-                    unique_paths.insert(path.clone(), id);
-                }
-            }
-            let current_paths = unique_paths
-                .into_iter()
-                .sorted_by_key(|(_, id)| *id)
-                .map(|(path, _)| path)
-                .collect::<Vec<_>>();
-            cx.update(|cx| {
-                cx.clear_recent_documents();
-                cx.add_recent_documents(&current_paths);
-            })
-        })
-    }
-
     pub(crate) fn load_workspace(
         serialized_workspace: SerializedWorkspace,
         paths_to_open: Vec<Option<ProjectPath>>,