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 mode: Mode,
52    pub boxes: Boxes,
53    pub history: JumpHistory,
54    pub search_box: SearchBox,
55    pub message_box: ErrorBox,
56    pub help_box: HelpBox,
57    pub link_box: LinkBox,
58}
59
60impl App {
61    pub fn reset(&mut self) {
62        self.vertical_scroll = 0;
63        self.selected = false;
64        self.select_index = 0;
65        self.boxes = Boxes::None;
66        self.help_box.close();
67    }
68
69    pub fn set_width(&mut self, width: u16) -> bool {
70        let temp_width = self.width;
71        self.width = cmp::min(width, GENERAL_CONFIG.width);
72        temp_width != self.width
73    }
74
75    #[must_use]
76    pub fn width(&self) -> u16 {
77        self.width
78    }
79}
80
81pub enum LinkType<'a> {
82    Internal(&'a str),
83    External(&'a str),
84    MarkdownFile(&'a str),
85}
86
87impl<'a> From<&'a str> for LinkType<'a> {
88    fn from(s: &'a str) -> Self {
89        if s.starts_with('#') {
90            return Self::Internal(s);
91        }
92        if s.ends_with("md") || !s.contains('.') {
93            return Self::MarkdownFile(s);
94        }
95        Self::External(s)
96    }
97}
98
99pub fn destruct_terminal() {
100    disable_raw_mode().unwrap();
101    execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
102    execute!(io::stdout(), cursor::Show).unwrap();
103}
104
105#[derive(Debug, Clone)]
106pub struct JumpHistory {
107    history: Vec<Jump>,
108}
109
110impl JumpHistory {
111    #[must_use]
112    pub fn new() -> Self {
113        Self {
114            history: Vec::new(),
115        }
116    }
117
118    pub fn push(&mut self, jump: Jump) {
119        self.history.push(jump);
120    }
121
122    pub fn pop(&mut self) -> Jump {
123        if let Some(jump) = self.history.pop() {
124            jump
125        } else {
126            Jump::FileTree
127        }
128    }
129}
130
131impl Default for JumpHistory {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137#[derive(Debug, Clone, PartialEq)]
138pub enum Jump {
139    File(String),
140    FileTree,
141}
142
143#[cfg(test)]
144#[test]
145fn test_jump_history() {
146    let mut jump_history = JumpHistory::default();
147    jump_history.push(Jump::File("file".to_string()));
148    jump_history.push(Jump::File("file2".to_string()));
149    jump_history.push(Jump::FileTree);
150    assert_eq!(jump_history.pop(), Jump::FileTree);
151    assert_eq!(jump_history.pop(), Jump::File("file2".to_string()));
152    assert_eq!(jump_history.pop(), Jump::File("file".to_string()));
153    assert_eq!(jump_history.pop(), Jump::FileTree);
154    assert_eq!(jump_history.pop(), Jump::FileTree);
155    assert_eq!(jump_history.pop(), Jump::FileTree);
156}