1use std::{path::PathBuf, sync::Arc};
2
3use fs::Fs;
4use gpui::{AppContext, Entity, Global, MenuItem};
5use smallvec::SmallVec;
6use ui::{App, Context};
7use util::{ResultExt, paths::PathExt};
8
9use crate::{
10 NewWindow, SerializedWorkspaceLocation, WORKSPACE_DB, WorkspaceId, path_list::PathList,
11};
12
13pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
14 let manager = cx.new(|_| HistoryManager::new());
15 HistoryManager::set_global(manager.clone(), cx);
16 HistoryManager::init(manager, fs, cx);
17}
18
19pub struct HistoryManager {
20 /// The history of workspaces that have been opened in the past, in reverse order.
21 /// The most recent workspace is at the end of the vector.
22 history: Vec<HistoryManagerEntry>,
23}
24
25#[derive(Debug)]
26pub struct HistoryManagerEntry {
27 pub id: WorkspaceId,
28 pub path: SmallVec<[PathBuf; 2]>,
29}
30
31struct GlobalHistoryManager(Entity<HistoryManager>);
32
33impl Global for GlobalHistoryManager {}
34
35impl HistoryManager {
36 fn new() -> Self {
37 Self {
38 history: Vec::new(),
39 }
40 }
41
42 fn init(this: Entity<HistoryManager>, fs: Arc<dyn Fs>, cx: &App) {
43 cx.spawn(async move |cx| {
44 let recent_folders = WORKSPACE_DB
45 .recent_workspaces_on_disk(fs.as_ref())
46 .await
47 .unwrap_or_default()
48 .into_iter()
49 .rev()
50 .filter_map(|(id, location, paths, _timestamp)| {
51 if matches!(location, SerializedWorkspaceLocation::Local) {
52 Some(HistoryManagerEntry::new(id, &paths))
53 } else {
54 None
55 }
56 })
57 .collect::<Vec<_>>();
58 this.update(cx, |this, cx| {
59 this.history = recent_folders;
60 this.update_jump_list(cx);
61 })
62 })
63 .detach();
64 }
65
66 pub fn global(cx: &App) -> Option<Entity<Self>> {
67 cx.try_global::<GlobalHistoryManager>()
68 .map(|model| model.0.clone())
69 }
70
71 fn set_global(history_manager: Entity<Self>, cx: &mut App) {
72 cx.set_global(GlobalHistoryManager(history_manager));
73 }
74
75 pub fn update_history(
76 &mut self,
77 id: WorkspaceId,
78 entry: HistoryManagerEntry,
79 cx: &mut Context<'_, HistoryManager>,
80 ) {
81 if let Some(pos) = self.history.iter().position(|e| e.id == id) {
82 self.history.remove(pos);
83 }
84 self.history.push(entry);
85 self.update_jump_list(cx);
86 }
87
88 pub fn delete_history(&mut self, id: WorkspaceId, cx: &mut Context<'_, HistoryManager>) {
89 let Some(pos) = self.history.iter().position(|e| e.id == id) else {
90 return;
91 };
92 self.history.remove(pos);
93 self.update_jump_list(cx);
94 }
95
96 fn update_jump_list(&mut self, cx: &mut Context<'_, HistoryManager>) {
97 let menus = vec![MenuItem::action("New Window", NewWindow)];
98 let entries = self
99 .history
100 .iter()
101 .rev()
102 .map(|entry| entry.path.clone())
103 .collect::<Vec<_>>();
104 let user_removed = cx.update_jump_list(menus, entries);
105 cx.spawn(async move |this, cx| {
106 let user_removed = user_removed.await;
107 if user_removed.is_empty() {
108 return;
109 }
110 let mut deleted_ids = Vec::new();
111 if let Ok(()) = this.update(cx, |this, _| {
112 for idx in (0..this.history.len()).rev() {
113 if let Some(entry) = this.history.get(idx)
114 && user_removed.contains(&entry.path)
115 {
116 deleted_ids.push(entry.id);
117 this.history.remove(idx);
118 }
119 }
120 }) {
121 for id in deleted_ids.iter() {
122 WORKSPACE_DB.delete_workspace_by_id(*id).await.log_err();
123 }
124 }
125 })
126 .detach();
127 }
128}
129
130impl HistoryManagerEntry {
131 pub fn new(id: WorkspaceId, paths: &PathList) -> Self {
132 let path = paths
133 .ordered_paths()
134 .map(|path| path.compact())
135 .collect::<SmallVec<[PathBuf; 2]>>();
136 Self { id, path }
137 }
138}