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}