Skip to main content

fm/modes/menu/
temp_marks.rs

1use std::{
2    cmp::min,
3    path::{Path, PathBuf},
4};
5
6use ratatui::{
7    layout::Rect,
8    style::Color,
9    text::Line,
10    widgets::{Paragraph, Widget},
11    Frame,
12};
13
14use crate::config::{ColorG, Gradient, MENU_STYLES};
15use crate::io::Offseted;
16use crate::log_info;
17use crate::modes::ContentWindow;
18use crate::{colored_skip_take, impl_content, impl_selectable};
19
20/// Temporary marks are saved in memory and reset when the application quit.
21///
22/// We save a fixed size vector of pathbufs.
23/// The user can set a mark (default bind alt+") and jump to it (default bind ").
24pub struct TempMarks {
25    content: Vec<Option<PathBuf>>,
26    pub index: usize,
27}
28
29impl Default for TempMarks {
30    fn default() -> Self {
31        let content = vec![None; Self::NB_TEMP_MARKS];
32        let index = 0;
33        Self { content, index }
34    }
35}
36
37impl TempMarks {
38    const NB_TEMP_MARKS: usize = 10;
39
40    fn log_index_error(index: usize) {
41        log_info!(
42            "index {index} is too big for a temp mark. Should be between 0 and {NB_TEMP_MARKS} excluded",
43            NB_TEMP_MARKS=Self::NB_TEMP_MARKS
44        );
45    }
46
47    /// Set the mark at given index to the given path.
48    pub fn set_mark(&mut self, index: usize, path: PathBuf) {
49        self.remove_path(&path);
50        if index >= Self::NB_TEMP_MARKS {
51            Self::log_index_error(index);
52            return;
53        }
54        self.content[index] = Some(path);
55    }
56
57    /// Reset the selected mark to `None`
58    pub fn erase_current_mark(&mut self) {
59        self.content[self.index] = None;
60    }
61
62    /// Get the indexed mark. `None` if the mark isn't set.
63    pub fn get_mark(&self, index: usize) -> &Option<PathBuf> {
64        if index >= Self::NB_TEMP_MARKS {
65            Self::log_index_error(index);
66            return &None;
67        }
68        &self.content[index]
69    }
70
71    /// Render the marks on the screen.
72    /// Can't use the common trait nor the macro since `Option<PathBuf>` doesn't implement `CowStr`.
73    pub fn draw_menu(&self, f: &mut Frame, rect: &Rect, window: &ContentWindow) {
74        let mut p_rect = rect.offseted(2, 3);
75        p_rect.height = p_rect.height.saturating_sub(2);
76        let content = self.content();
77        let lines: Vec<_> = colored_skip_take!(content, window)
78            .filter(|(index, _, _)| {
79                (*index) as u16 + ContentWindow::WINDOW_MARGIN_TOP_U16 + 1 - window.top as u16 + 2
80                    <= rect.height
81            })
82            .map(|(index, opt_path, style)| {
83                let content = if let Some(path) = opt_path {
84                    format!("{index} {p}", p = path.display())
85                } else {
86                    format!("{index} ")
87                };
88                Line::styled(content, self.style(index, &style))
89            })
90            .collect();
91        Paragraph::new(lines).render(p_rect, f.buffer_mut());
92    }
93
94    pub fn digit_for(&self, path: &Path) -> Option<usize> {
95        for (index, marked_path) in self.content.iter().enumerate() {
96            match marked_path {
97                Some(p) if p == path => return Some(index),
98                _ => (),
99            }
100        }
101        None
102    }
103
104    /// Update the temp mark associated to `old_path`.
105    /// Does nothing if `old_path` isn't associated to a temp mark.
106    pub fn move_path(&mut self, old_path: &Path, new_path: &Path) {
107        let Some(index) = self.digit_for(old_path) else {
108            return;
109        };
110        self.set_mark(index, new_path.to_path_buf());
111    }
112
113    /// Reset the temp mark associated to `old_path`.
114    /// Does nothing if no mark is set for `old_path`.
115    pub fn remove_path(&mut self, old_path: &Path) {
116        for index in 0..Self::NB_TEMP_MARKS {
117            match &self.content[index] {
118                Some(path) if path == old_path => self.content[index] = None,
119                _ => (),
120            }
121        }
122    }
123}
124
125type Opb = Option<PathBuf>;
126
127impl_content!(TempMarks, Opb);