Skip to main content

editor/
recent_projects.rs

1use std::fs;
2use std::path::PathBuf;
3
4use crate::project::MANIFEST_FILE;
5
6const RECENT_PROJECTS_FILE: &str = "recent_projects.ron";
7const MAX_ENTRIES: usize = 10;
8
9/// The menu scene's "recently opened" list: project root folders, most recent first,
10/// persisted next to the editor executable so it survives between runs.
11#[derive(Default, serde::Serialize, serde::Deserialize)]
12pub struct RecentProjects {
13    pub roots: Vec<PathBuf>,
14}
15
16impl RecentProjects {
17    fn file_path() -> PathBuf {
18        std::env::current_exe()
19            .ok()
20            .and_then(|exe| exe.parent().map(|dir| dir.join(RECENT_PROJECTS_FILE)))
21            .unwrap_or_else(|| PathBuf::from(RECENT_PROJECTS_FILE))
22    }
23
24    /// Loads the persisted list, dropping any entries whose project no longer exists on disk
25    /// (moved, deleted, on an unmounted drive, ...) so the menu never offers a dead shortcut.
26    pub fn load() -> Self {
27        let Ok(text) = fs::read_to_string(Self::file_path()) else { return Self::default() };
28        let mut recent: Self = ron::from_str(&text).unwrap_or_default();
29        recent.roots.retain(|root| root.join(MANIFEST_FILE).is_file());
30        recent
31    }
32
33    pub fn save(&self) -> anyhow::Result<()> {
34        fs::write(Self::file_path(), ron::ser::to_string_pretty(self, Default::default())?)?;
35        Ok(())
36    }
37
38    /// Moves `root` to the front of the list (adding it if new), capped at [`MAX_ENTRIES`].
39    pub fn add(&mut self, root: PathBuf) {
40        self.roots.retain(|existing| existing != &root);
41        self.roots.insert(0, root);
42        self.roots.truncate(MAX_ENTRIES);
43    }
44}