Skip to main content

fm/modes/menu/
context.rs

1use std::fmt::Formatter;
2use std::fs::Metadata;
3use std::time::SystemTime;
4
5use strum::{EnumIter, IntoEnumIterator};
6
7use crate::event::ActionMap;
8use crate::io::Opener;
9use crate::modes::{extract_datetime, ExtensionKind, FileInfo};
10use crate::{impl_content, impl_draw_menu_with_char, impl_selectable};
11
12const CONTEXT_ACTIONS: [(&str, ActionMap); 10] = [
13    ("Open", ActionMap::OpenFile),
14    ("Open with", ActionMap::Exec),
15    ("Open in Neovim", ActionMap::NvimFilepicker),
16    ("Flag", ActionMap::ToggleFlag),
17    ("Rename", ActionMap::Rename),
18    ("Delete", ActionMap::Delete),
19    ("Trash", ActionMap::TrashMoveFile),
20    ("Chmod", ActionMap::Chmod),
21    ("New File", ActionMap::NewFile),
22    ("New Directory", ActionMap::NewDir),
23];
24
25/// Context menu of a file.
26/// A few possible actions and some more information about this file.
27#[derive(Default)]
28pub struct ContextMenu {
29    pub content: Vec<&'static str>,
30    index: usize,
31    actions: Vec<&'static ActionMap>,
32}
33
34impl ContextMenu {
35    pub fn setup(&mut self) {
36        self.content = CONTEXT_ACTIONS.iter().map(|(s, _)| *s).collect();
37        self.actions = CONTEXT_ACTIONS.iter().map(|(_, a)| a).collect();
38    }
39
40    pub fn matcher(&self) -> &ActionMap {
41        self.actions[self.index]
42    }
43
44    pub fn reset(&mut self) {
45        self.index = 0;
46    }
47}
48
49type StaticStr = &'static str;
50
51impl_content!(ContextMenu, StaticStr);
52impl_draw_menu_with_char!(ContextMenu, StaticStr);
53
54/// Used to generate more informations about a file in the context menu.
55pub struct MoreInfos<'a> {
56    file_info: &'a FileInfo,
57    opener: &'a Opener,
58}
59
60impl<'a> MoreInfos<'a> {
61    pub fn new(file_info: &'a FileInfo, opener: &'a Opener) -> Self {
62        Self { file_info, opener }
63    }
64
65    /// Informations about the file as an array of strings.
66    pub fn to_lines(&self) -> [String; 8] {
67        let mut times = self.system_times();
68        [
69            self.file_path(),
70            self.owner_group(),
71            self.perms(),
72            self.size_inode(),
73            std::mem::take(&mut times[0]),
74            std::mem::take(&mut times[1]),
75            std::mem::take(&mut times[2]),
76            self.kind_opener(),
77        ]
78    }
79
80    fn file_path(&self) -> String {
81        let mut ret = format!("Filepath:    {path}", path = self.file_info.path.display());
82        if self.file_info.is_symlink() {
83            self.file_info.expand_symlink(&mut ret);
84        }
85        ret
86    }
87
88    fn owner_group(&self) -> String {
89        format!(
90            "Owner/Group: {owner} / {group}",
91            owner = self.file_info.owner,
92            group = self.file_info.group
93        )
94    }
95
96    fn perms(&self) -> String {
97        if let Ok(perms) = self.file_info.permissions() {
98            format!(
99                "Permissions: {dir_symbol}{perms}",
100                dir_symbol = self.file_info.dir_symbol()
101            )
102        } else {
103            "".to_owned()
104        }
105    }
106
107    fn size_inode(&self) -> String {
108        format!(
109            "{size_kind} {size} / Inode: {inode}",
110            size_kind = self.file_info.file_kind.size_description(),
111            size = self.file_info.size_column.trimed(),
112            inode = self.file_info.ino()
113        )
114    }
115
116    fn kind_opener(&self) -> String {
117        if self.file_info.file_kind.is_normal_file() {
118            let ext_kind = ExtensionKind::matcher(&self.file_info.extension.to_lowercase());
119            if let Some(opener) = self.opener.kind(&self.file_info.path) {
120                format!("Opener: {opener}, Previewer: {ext_kind}")
121            } else {
122                format!("Previewer:  {ext_kind}")
123            }
124        } else {
125            let kind = self.file_info.file_kind.long_description();
126            format!("Kind:        {kind}")
127        }
128    }
129
130    fn system_times(&self) -> Vec<String> {
131        let Ok(metadata) = &self.file_info.metadata() else {
132            return vec!["".to_owned(), "".to_owned(), "".to_owned()];
133        };
134        TimeKind::iter()
135            .map(|time_kind| time_kind.format_time(metadata))
136            .collect()
137    }
138}
139
140#[derive(EnumIter)]
141enum TimeKind {
142    Modified,
143    Created,
144    Accessed,
145}
146
147impl TimeKind {
148    fn read_time(&self, metadata: &Metadata) -> Result<SystemTime, std::io::Error> {
149        match self {
150            Self::Modified => metadata.modified(),
151            Self::Created => metadata.created(),
152            Self::Accessed => metadata.accessed(),
153        }
154    }
155
156    fn format_time(&self, metadata: &Metadata) -> String {
157        let Ok(dt) = self.read_time(metadata) else {
158            return "".to_owned();
159        };
160        let formated_time = extract_datetime(dt).unwrap_or_default();
161        format!("{self}{formated_time}")
162    }
163}
164
165impl std::fmt::Display for TimeKind {
166    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
167        match self {
168            Self::Modified => write!(f, "Modified:    ",),
169            Self::Created => write!(f, "Created:     ",),
170            Self::Accessed => write!(f, "Assessed:    "),
171        }
172    }
173}