history_manager.rs

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