Skip to main content

md_tui/
util.rs

1use std::{cmp, io};
2
3use crossterm::{
4    cursor,
5    event::DisableMouseCapture,
6    execute,
7    terminal::{LeaveAlternateScreen, disable_raw_mode},
8};
9use general::GENERAL_CONFIG;
10
11use crate::boxes::{errorbox::ErrorBox, help_box::HelpBox, linkbox::LinkBox, searchbox::SearchBox};
12
13pub mod colors;
14pub mod general;
15pub mod keys;
16
17#[derive(Debug, Clone, Copy, PartialEq, Default)]
18pub enum Mode {
19    View,
20    #[default]
21    FileTree,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Default)]
25pub enum Boxes {
26    Error,
27    Search,
28    LinkPreview,
29    #[default]
30    None,
31}
32
33impl From<JumpHistory> for Mode {
34    fn from(jump_history: JumpHistory) -> Self {
35        match jump_history.history.last() {
36            Some(jump) => match jump {
37                Jump::File(_) => Mode::View,
38                Jump::FileTree => Mode::FileTree,
39            },
40            None => Mode::FileTree,
41        }
42    }
43}
44
45#[derive(Default, Clone)]
46pub struct App {
47    pub vertical_scroll: u16,
48    width: u16,
49    pub selected: bool,
50    pub select_index: usize,
51    pub details_selected: bool,
52    pub details_select_index: usize,
53    pub mode: Mode,
54    pub boxes: Boxes,
55    pub history: JumpHistory,
56    pub search_box: SearchBox,
57    pub message_box: ErrorBox,
58    pub help_box: HelpBox,
59    pub link_box: LinkBox,
60}
61
62impl App {
63    pub fn reset(&mut self) {
64        self.vertical_scroll = 0;
65        self.selected = false;
66        self.select_index = 0;
67        self.details_selected = false;
68        self.details_select_index = 0;
69        self.boxes = Boxes::None;
70        self.help_box.close();
71    }
72
73    pub fn set_width(&mut self, width: u16) -> bool {
74        let temp_width = self.width;
75        self.width = cmp::min(width, GENERAL_CONFIG.width);
76        temp_width != self.width
77    }
78
79    #[must_use]
80    pub fn width(&self) -> u16 {
81        self.width
82    }
83}
84
85pub enum LinkType<'a> {
86    Internal(&'a str),
87    External(&'a str),
88    MarkdownFile(&'a str),
89}
90
91impl<'a> From<&'a str> for LinkType<'a> {
92    fn from(s: &'a str) -> Self {
93        if s.starts_with('#') {
94            return Self::Internal(s);
95        }
96        if s.ends_with("md") || !s.contains('.') {
97            return Self::MarkdownFile(s);
98        }
99        Self::External(s)
100    }
101}
102
103pub fn destruct_terminal() {
104    disable_raw_mode().unwrap();
105    execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
106    execute!(io::stdout(), cursor::Show).unwrap();
107}
108
109#[derive(Debug, Clone)]
110pub struct JumpHistory {
111    history: Vec<Jump>,
112}
113
114impl JumpHistory {
115    #[must_use]
116    pub fn new() -> Self {
117        Self {
118            history: Vec::new(),
119        }
120    }
121
122    pub fn push(&mut self, jump: Jump) {
123        self.history.push(jump);
124    }
125
126    pub fn pop(&mut self) -> Jump {
127        if let Some(jump) = self.history.pop() {
128            jump
129        } else {
130            Jump::FileTree
131        }
132    }
133}
134
135impl Default for JumpHistory {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141#[derive(Debug, Clone, PartialEq)]
142pub enum Jump {
143    File(String),
144    FileTree,
145}
146
147#[cfg(test)]
148#[test]
149fn test_jump_history() {
150    let mut jump_history = JumpHistory::default();
151    jump_history.push(Jump::File("file".to_string()));
152    jump_history.push(Jump::File("file2".to_string()));
153    jump_history.push(Jump::FileTree);
154    assert_eq!(jump_history.pop(), Jump::FileTree);
155    assert_eq!(jump_history.pop(), Jump::File("file2".to_string()));
156    assert_eq!(jump_history.pop(), Jump::File("file".to_string()));
157    assert_eq!(jump_history.pop(), Jump::FileTree);
158    assert_eq!(jump_history.pop(), Jump::FileTree);
159    assert_eq!(jump_history.pop(), Jump::FileTree);
160}