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

@ -14,10 +14,7 @@ use itertools::Itertools;
use parking_lot::RwLock;
use smallvec::SmallVec;
use windows::{
UI::{
StartScreen::{JumpList, JumpListItem},
ViewManagement::UISettings,
},
UI::ViewManagement::UISettings,
Win32::{
Foundation::*,
Graphics::{
@ -52,7 +49,7 @@ pub(crate) struct WindowsPlatform {
pub(crate) struct WindowsPlatformState {
callbacks: PlatformCallbacks,
menus: Vec<OwnedMenu>,
dock_menu_actions: Vec<Box<dyn Action>>,
jump_list: JumpList,
// NOTE: standard cursor handles don't need to close.
pub(crate) current_cursor: Option<HCURSOR>,
}
@ -70,12 +67,12 @@ struct PlatformCallbacks {
impl WindowsPlatformState {
fn new() -> Self {
let callbacks = PlatformCallbacks::default();
let dock_menu_actions = Vec::new();
let jump_list = JumpList::new();
let current_cursor = load_cursor(CursorStyle::Arrow);
Self {
callbacks,
dock_menu_actions,
jump_list,
current_cursor,
menus: Vec::new(),
}
@ -189,9 +186,10 @@ impl WindowsPlatform {
let mut lock = self.state.borrow_mut();
if let Some(mut callback) = lock.callbacks.app_menu_action.take() {
let Some(action) = lock
.dock_menu_actions
.jump_list
.dock_menus
.get(action_idx)
.map(|action| action.boxed_clone())
.map(|dock_menu| dock_menu.action.boxed_clone())
else {
lock.callbacks.app_menu_action = Some(callback);
log::error!("Dock menu for index {action_idx} not found");
@ -254,33 +252,35 @@ impl WindowsPlatform {
false
}
fn configure_jump_list(&self, menus: Vec<MenuItem>) -> Result<()> {
let jump_list = JumpList::LoadCurrentAsync()?.get()?;
let items = jump_list.Items()?;
items.Clear()?;
fn set_dock_menus(&self, menus: Vec<MenuItem>) {
let mut actions = Vec::new();
for item in menus.into_iter() {
let item = match item {
MenuItem::Separator => JumpListItem::CreateSeparator()?,
MenuItem::Submenu(_) => {
log::error!("Set `MenuItemSubmenu` for dock menu on Windows is not supported.");
continue;
}
MenuItem::Action { name, action, .. } => {
let idx = actions.len();
actions.push(action.boxed_clone());
let item_args = format!("--dock-action {}", idx);
JumpListItem::CreateWithArguments(
&HSTRING::from(item_args),
&HSTRING::from(name.as_ref()),
)?
}
};
items.Append(&item)?;
}
jump_list.SaveAsync()?.get()?;
self.state.borrow_mut().dock_menu_actions = actions;
Ok(())
menus.into_iter().for_each(|menu| {
if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
actions.push(dock_menu);
}
});
let mut lock = self.state.borrow_mut();
lock.jump_list.dock_menus = actions;
update_jump_list(&lock.jump_list).log_err();
}
fn update_jump_list(
&self,
menus: Vec<MenuItem>,
entries: Vec<SmallVec<[PathBuf; 2]>>,
) -> Vec<SmallVec<[PathBuf; 2]>> {
let mut actions = Vec::new();
menus.into_iter().for_each(|menu| {
if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
actions.push(dock_menu);
}
});
let mut lock = self.state.borrow_mut();
lock.jump_list.dock_menus = actions;
lock.jump_list.recent_workspaces = entries;
update_jump_list(&lock.jump_list)
.log_err()
.unwrap_or_default()
}
}
@ -535,7 +535,7 @@ impl Platform for WindowsPlatform {
}
fn set_dock_menu(&self, menus: Vec<MenuItem>, _keymap: &Keymap) {
self.configure_jump_list(menus).log_err();
self.set_dock_menus(menus);
}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
@ -673,6 +673,14 @@ impl Platform for WindowsPlatform {
.log_err();
}
}
fn update_jump_list(
&self,
menus: Vec<MenuItem>,
entries: Vec<SmallVec<[PathBuf; 2]>>,
) -> Vec<SmallVec<[PathBuf; 2]>> {
self.update_jump_list(menus, entries)
}
}
impl Drop for WindowsPlatform {