history_manager.rs

  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}