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, 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}