fm/modes/menu/
shortcut.rs

1use std::borrow::Borrow;
2// use std::cmp::min;
3use std::path::{Path, PathBuf};
4use std::str::FromStr;
5
6use crate::common::{
7    current_uid, path_to_config_folder, tilde, HARDCODED_SHORTCUTS, TRASH_FOLDER_FILES,
8};
9// use crate::config::{ColorG, Gradient, MENU_STYLES};
10use crate::io::git_root;
11use crate::{impl_content, impl_draw_menu_with_char, impl_selectable, log_info};
12
13/// Holds the hardcoded and mountpoints shortcuts the user can jump to.
14/// Also know which shortcut is currently selected by the user.
15#[derive(Debug, Clone)]
16pub struct Shortcut {
17    /// The path to the shortcuts. It's a vector since we can't know how much
18    /// mount points are defined.
19    pub content: Vec<PathBuf>,
20    /// The currently selected shortcut
21    pub index: usize,
22    start_folder: PathBuf,
23}
24
25impl Shortcut {
26    /// Creates empty shortcuts.
27    /// content won't be initiated before the first opening of the menu.
28    #[must_use]
29    pub fn empty(start_folder: &Path) -> Self {
30        let content = vec![];
31        Self {
32            content,
33            index: 0,
34            start_folder: start_folder.to_owned(),
35        }
36    }
37
38    fn build_content(start_folder: &Path) -> Vec<PathBuf> {
39        let mut content = Self::hardcoded_shortcuts();
40        Self::push_home_path(&mut content);
41        Self::push_trash_folder(&mut content);
42        Self::push_config_folder(&mut content);
43        Self::push_start_folder(&mut content, start_folder);
44        Self::push_git_root(&mut content);
45        content
46    }
47
48    pub fn update(&mut self) {
49        self.content = Self::build_content(&self.start_folder)
50    }
51
52    fn hardcoded_shortcuts() -> Vec<PathBuf> {
53        HARDCODED_SHORTCUTS.iter().map(PathBuf::from).collect()
54    }
55
56    /// Insert a shortcut to home directory of the current user.
57    fn push_home_path(shortcuts: &mut Vec<PathBuf>) {
58        let Ok(home_path) = PathBuf::from_str(tilde("~").borrow());
59        shortcuts.push(home_path);
60    }
61
62    /// Insert a shortcut to trash directory of the current user.
63    fn push_trash_folder(shortcuts: &mut Vec<PathBuf>) {
64        let Ok(trash_path) = PathBuf::from_str(tilde(TRASH_FOLDER_FILES).borrow());
65        if trash_path.exists() {
66            shortcuts.push(trash_path);
67        }
68    }
69
70    /// Insert a shortcut to config file directory of the current user.
71    fn push_config_folder(shortcuts: &mut Vec<PathBuf>) {
72        if let Ok(config_folder) = path_to_config_folder() {
73            shortcuts.push(config_folder);
74        }
75    }
76
77    fn push_start_folder(shortcuts: &mut Vec<PathBuf>, start_folder: &Path) {
78        shortcuts.push(start_folder.to_owned());
79    }
80
81    fn git_root_or_cwd() -> PathBuf {
82        git_root().map_or_else(
83            |_| std::env::current_dir().unwrap_or_default(),
84            PathBuf::from,
85        )
86    }
87
88    fn push_git_root(shortcuts: &mut Vec<PathBuf>) {
89        shortcuts.push(Self::git_root_or_cwd());
90    }
91
92    fn clear_doublons(&mut self) {
93        self.content.sort_unstable();
94        self.content.dedup();
95    }
96
97    /// Update the shortcuts with the mount points.
98    fn extend_with_mount_points(&mut self, mount_points: &[&Path]) {
99        self.content
100            .extend(mount_points.iter().map(|p| p.to_path_buf()));
101        self.extend_with_mtp();
102        self.clear_doublons();
103    }
104
105    /// Update the shortcuts with MTP mount points
106    fn extend_with_mtp(&mut self) {
107        let Ok(uid) = current_uid() else {
108            return;
109        };
110        let mtp_mount_point = PathBuf::from(format!("/run/user/{uid}/gvfs/"));
111        if !mtp_mount_point.exists() || !mtp_mount_point.is_dir() {
112            return;
113        }
114
115        let mount_points: Vec<PathBuf> = match std::fs::read_dir(&mtp_mount_point) {
116            Ok(read_dir) => read_dir
117                .filter_map(std::result::Result::ok)
118                .filter(|direntry| direntry.path().is_dir())
119                .map(|direntry| direntry.path())
120                .collect(),
121            Err(error) => {
122                log_info!(
123                    "unreadable gvfs {mtp_mount_point}: {error:?} ",
124                    mtp_mount_point = mtp_mount_point.display(),
125                );
126                return;
127            }
128        };
129        self.content.extend(mount_points);
130    }
131
132    /// Refresh the shortcuts.
133    /// Lazy loading.
134    /// As long as this method isn't called, content won't be populated.
135    pub fn refresh(
136        &mut self,
137        mount_points: &[&Path],
138        left_path: &std::path::Path,
139        right_path: &std::path::Path,
140    ) {
141        self.content = Self::build_content(&self.start_folder);
142        self.content.push(left_path.to_owned());
143        self.content.push(right_path.to_owned());
144        self.extend_with_mount_points(mount_points);
145    }
146}
147
148impl_content!(Shortcut, PathBuf);
149impl_draw_menu_with_char!(Shortcut, PathBuf);