History manager (#26369)

While working on implementing `add_recent_documents` for Windows, I
found that the process is significantly more complex compared to macOS.
On macOS, simply registering the `add_recent_documents` function is
enough, as the system handles everything automatically.

On Windows, however, there are two cases to consider:  
- **Files opened by the app**: These appear in the "Recent" section (as
shown in the screenshot, "test.txt") and are managed automatically by
Windows (by setting windows registry), similar to macOS.

![屏幕截图 2025-03-10
230738](https://github.com/user-attachments/assets/8fc8063b-4369-43cc-aaaf-7370a7d27060)


- **Folders opened by the app**: This is more complicated because
Windows does not handle it automatically, requiring the application to
track opened folders manually.

To address this, this PR introduces a `History Manager` along with
`HistoryManagerEvent::Update` and `HistoryManagerEvent::Delete` events
to simplify the process of managing recently opened folders.



https://github.com/user-attachments/assets/a2581c15-7653-4faf-96b0-7c48ab1dcc8d



Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
张小白 2025-04-12 05:34:51 +08:00 committed by GitHub
parent 5734ffbb18
commit a5fe6d1e61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 482 additions and 66 deletions

View file

@ -1,4 +1,5 @@
pub mod dock;
pub mod history_manager;
pub mod item;
mod modal_layer;
pub mod notifications;
@ -43,6 +44,7 @@ use gpui::{
WindowHandle, WindowId, WindowOptions, action_as, actions, canvas, impl_action_as,
impl_actions, point, relative, size, transparent_black,
};
pub use history_manager::*;
pub use item::{
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
@ -387,6 +389,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
component::init();
theme_preview::init(cx);
toast_layer::init(cx);
history_manager::init(cx);
cx.on_action(Workspace::close_global);
cx.on_action(reload);
@ -902,6 +905,9 @@ impl Workspace {
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
this.update_window_title(window, cx);
this.serialize_workspace(window, cx);
// This event could be triggered by `AddFolderToProject` or `RemoveFromProject`.
// So we need to update the history.
this.update_history(cx);
}
project::Event::DisconnectedFromHost => {
@ -1334,7 +1340,10 @@ impl Workspace {
.unwrap_or_default();
window
.update(cx, |_, window, _| window.activate_window())
.update(cx, |workspace, window, cx| {
window.activate_window();
workspace.update_history(cx);
})
.log_err();
Ok((window, opened_items))
})
@ -4707,19 +4716,7 @@ impl Workspace {
}
}
let location = if let Some(ssh_project) = &self.serialized_ssh_project {
Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
} else if let Some(local_paths) = self.local_paths(cx) {
if !local_paths.is_empty() {
Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
} else {
None
}
} else {
None
};
if let Some(location) = location {
if let Some(location) = self.serialize_workspace_location(cx) {
let breakpoints = self.project.update(cx, |project, cx| {
project.breakpoint_store().read(cx).all_breakpoints(cx)
});
@ -4739,13 +4736,42 @@ impl Workspace {
breakpoints,
window_id: Some(window.window_handle().window_id().as_u64()),
};
return window.spawn(cx, async move |_| {
persistence::DB.save_workspace(serialized_workspace).await
persistence::DB.save_workspace(serialized_workspace).await;
});
}
Task::ready(())
}
fn serialize_workspace_location(&self, cx: &App) -> Option<SerializedWorkspaceLocation> {
if let Some(ssh_project) = &self.serialized_ssh_project {
Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
} else if let Some(local_paths) = self.local_paths(cx) {
if !local_paths.is_empty() {
Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
} else {
None
}
} else {
None
}
}
fn update_history(&self, cx: &mut App) {
let Some(id) = self.database_id() else {
return;
};
let Some(location) = self.serialize_workspace_location(cx) else {
return;
};
if let Some(manager) = HistoryManager::global(cx) {
manager.update(cx, |this, cx| {
this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
});
}
}
async fn serialize_items(
this: &WeakEntity<Self>,
items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
@ -6614,6 +6640,7 @@ async fn open_ssh_project_inner(
let mut workspace =
Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
workspace.set_serialized_ssh_project(serialized_ssh_project);
workspace.update_history(cx);
workspace
});
})?;