history_manager.rs

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