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