1use std::{
2 io::{self, Stdout},
3 sync::{Arc, RwLock},
4};
5
6use crossterm::{
7 event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
8 terminal::{self, disable_raw_mode, enable_raw_mode},
9 ExecutableCommand, QueueableCommand,
10};
11use log::debug;
12use log4rs::Handle;
13
14use crate::form::Form;
15
16#[derive(Debug, Copy, Clone, PartialEq)]
18pub enum EventResult {
19 Submit,
21 Abort,
23 ToggleDebug,
25 None,
27}
28
29#[derive(Debug, Copy, Clone, PartialEq)]
30pub enum EventHandlerResult {
31 Handled(EventResult),
32 NotHandled,
33}
34
35pub struct App {
37 stdout: Stdout,
38 _logger_handle: log4rs::Handle,
39 logging_enabled: Arc<RwLock<bool>>,
40}
41
42impl App {
43 pub fn with_writer(stdout: Stdout) -> Self {
49 let logging_enabled = Arc::new(RwLock::new(false));
50
51 let logger_handle = Self::configure_logging(logging_enabled.clone());
52
53 Self {
54 stdout,
55 _logger_handle: logger_handle,
56 logging_enabled,
57 }
58 }
59
60 pub fn init(&mut self) -> io::Result<()> {
65 self.init_terminal()?;
66 self.install_panic_handler();
67
68 Ok(())
69 }
70
71 fn install_panic_handler(&self) {
72 let original_hook = std::panic::take_hook();
73 std::panic::set_hook(Box::new(move |panic_info| {
74 let _ = Self::restore_terminal();
75 original_hook(panic_info);
76 }))
77 }
78
79 fn init_terminal(&mut self) -> io::Result<()> {
80 self.stdout.execute(terminal::EnterAlternateScreen)?;
81 self.stdout
82 .execute(terminal::Clear(terminal::ClearType::All))?;
83
84 enable_raw_mode()?;
85
86 Ok(())
87 }
88
89 fn restore_terminal() -> io::Result<()> {
90 disable_raw_mode()?;
91
92 io::stdout().execute(terminal::LeaveAlternateScreen)?;
93
94 Ok(())
95 }
96
97 pub fn toggle_log_output(&self) -> io::Result<()> {
99 if Self::is_logging_enabled(self.logging_enabled.clone()) {
100 self.disable_log_output()?;
101 } else {
102 self.enable_log_output()?;
103 }
104
105 Ok(())
106 }
107
108 fn enable_log_output(&self) -> io::Result<()> {
109 let mut logging_enabled = self
110 .logging_enabled
111 .write()
112 .expect("Unable to get write lock on log output toggle");
113 *logging_enabled = true;
114
115 drop(logging_enabled);
117
118 debug!("Log output enabled");
119
120 Ok(())
121 }
122 fn disable_log_output(&self) -> io::Result<()> {
123 let mut logging_enabled = self
124 .logging_enabled
125 .write()
126 .expect("Unable to get write lock on log output toggle");
127 *logging_enabled = false;
128
129 drop(logging_enabled);
131
132 std::io::stdout().queue(crossterm::terminal::Clear(
133 crossterm::terminal::ClearType::All,
134 ))?;
135
136 debug!("Log output disabled");
137
138 Ok(())
139 }
140
141 fn configure_logging(logging_enabled: Arc<RwLock<bool>>) -> Handle {
142 use log::LevelFilter;
143 use log4rs::append::file::FileAppender;
144 use log4rs::config::{Appender, Config, Root};
145 use log4rs::encode::pattern::PatternEncoder;
146
147 let log_buffer = crate::vec_appender::Appender::with_capacity(100);
148 let log_dialog = crate::dialog_appender::Appender::new((0, 26), 5, logging_enabled);
149
150 let debug_log = FileAppender::builder()
151 .encoder(Box::new(PatternEncoder::new("{l} - {m}{n}\n")))
152 .build("log/debug.log")
153 .unwrap();
154
155 let config = Config::builder()
156 .appender(Appender::builder().build("debug_log", Box::new(debug_log)))
157 .appender(Appender::builder().build("log_buffer", Box::new(log_buffer)))
158 .appender(Appender::builder().build("log_dialog", Box::new(log_dialog)))
159 .build(
160 Root::builder()
161 .appender("log_dialog")
162 .appender("debug_log")
163 .build(LevelFilter::Trace),
164 )
165 .unwrap();
166
167 log4rs::init_config(config).unwrap()
168 }
169
170 fn is_logging_enabled(logging_enabled: Arc<RwLock<bool>>) -> bool {
171 logging_enabled.read().map(|e| *e).unwrap_or(false)
172 }
173
174 pub fn keyboard_event(&mut self, form: &mut Form, ev: Event) -> io::Result<EventResult> {
177 match ev {
178 Event::Key(k) if k.code == KeyCode::Esc => {
179 return Ok(EventResult::Abort);
180 }
181 Event::Key(k) if k.code == KeyCode::Enter => {
182 return Ok(EventResult::Submit);
183 }
184 Event::Key(k) if k.code == KeyCode::Left => {
185 form.move_event(k.code);
186 }
187 Event::Key(k) if k.code == KeyCode::Right => {
188 form.move_event(k.code);
189 }
190 Event::Key(k) if k.code == KeyCode::Up => {
191 form.move_event(k.code);
192 }
193 Event::Key(k) if k.code == KeyCode::Down => {
194 form.move_event(k.code);
195 }
196 Event::Key(k)
197 if k.code == KeyCode::Char('d') && k.modifiers.contains(KeyModifiers::CONTROL) =>
198 {
199 return Ok(EventResult::ToggleDebug);
200 }
201 _ => (),
202 }
203
204 Ok(EventResult::None)
205 }
206
207 pub fn execute(&mut self, form: &mut Form) -> io::Result<EventResult> {
210 let mut output = EventResult::None;
211 loop {
212 form.display(&mut io::stdout())?;
213
214 let ev = event::read()?;
215
216 if let Event::Key(kev) = ev {
217 if kev == KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL) {
218 return Ok(EventResult::Abort);
219 }
220 }
221
222 debug!("Key event: {:?}", ev);
223
224 let form_result = form.event_handler(&ev)?;
225
226 let result = if let EventHandlerResult::Handled(form_result) = form_result {
228 form_result
229 } else {
230 self.keyboard_event(form, ev)?
231 };
232
233 debug!("Result: {:?}", result);
234
235 match result {
236 EventResult::Abort => break,
237 EventResult::Submit => {
238 output = EventResult::Submit;
239 break;
240 }
241 EventResult::ToggleDebug => {
242 self.toggle_log_output()?;
243 }
244 EventResult::None => (),
245 };
246 }
247
248 Ok(output)
249 }
250}
251
252impl Drop for App {
253 fn drop(&mut self) {
254 let _ = Self::restore_terminal();
255 }
256}