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        if let Ok(home_path) = PathBuf::from_str(tilde("~").borrow()) {
59            shortcuts.push(home_path);
60        }
61    }
62
63    /// Insert a shortcut to trash directory of the current user.
64    fn push_trash_folder(shortcuts: &mut Vec<PathBuf>) {
65        if let Ok(trash_path) = PathBuf::from_str(tilde(TRASH_FOLDER_FILES).borrow()) {
66            if trash_path.exists() {
67                shortcuts.push(trash_path);
68            }
69        }
70    }
71
72    /// Insert a shortcut to config file directory of the current user.
73    fn push_config_folder(shortcuts: &mut Vec<PathBuf>) {
74        if let Ok(config_folder) = path_to_config_folder() {
75            shortcuts.push(config_folder);
76        }
77    }
78
79    fn push_start_folder(shortcuts: &mut Vec<PathBuf>, start_folder: &Path) {
80        shortcuts.push(start_folder.to_owned());
81    }
82
83    fn git_root_or_cwd() -> PathBuf {
84        git_root().map_or_else(
85            |_| std::env::current_dir().unwrap_or_default(),
86            PathBuf::from,
87        )
88    }
89
90    fn push_git_root(shortcuts: &mut Vec<PathBuf>) {
91        shortcuts.push(Self::git_root_or_cwd());
92    }
93
94    fn clear_doublons(&mut self) {
95        self.content.sort_unstable();
96        self.content.dedup();
97    }
98
99    /// Update the shortcuts with the mount points.
100    fn extend_with_mount_points(&mut self, mount_points: &[&Path]) {
101        self.content
102            .extend(mount_points.iter().map(|p| p.to_path_buf()));
103        self.extend_with_mtp();
104        self.clear_doublons();
105    }
106
107    /// Update the shortcuts with MTP mount points
108    fn extend_with_mtp(&mut self) {
109        let Ok(uid) = current_uid() else {
110            return;
111        };
112        let mtp_mount_point = PathBuf::from(format!("/run/user/{uid}/gvfs/"));
113        if !mtp_mount_point.exists() || !mtp_mount_point.is_dir() {
114            return;
115        }
116
117        let mount_points: Vec<PathBuf> = match std::fs::read_dir(&mtp_mount_point) {
118            Ok(read_dir) => read_dir
119                .filter_map(std::result::Result::ok)
120                .filter(|direntry| direntry.path().is_dir())
121                .map(|direntry| direntry.path())
122                .collect(),
123            Err(error) => {
124                log_info!(
125                    "unreadable gvfs {mtp_mount_point}: {error:?} ",
126                    mtp_mount_point = mtp_mount_point.display(),
127                );
128                return;
129            }
130        };
131        self.content.extend(mount_points);
132    }
133
134    /// Refresh the shortcuts.
135    /// Lazy loading.
136    /// As long as this method isn't called, content won't be populated.
137    pub fn refresh(
138        &mut self,
139        mount_points: &[&Path],
140        left_path: &std::path::Path,
141        right_path: &std::path::Path,
142    ) {
143        self.content = Self::build_content(&self.start_folder);
144        self.content.push(left_path.to_owned());
145        self.content.push(right_path.to_owned());
146        self.extend_with_mount_points(mount_points);
147    }
148}
149
150impl_selectable!(Shortcut);
151impl_content!(Shortcut, PathBuf);
152impl_draw_menu_with_char!(Shortcut, PathBuf);