1use clap::{App, ArgMatches, ErrorKind};
2use crossterm::event::{poll, read, Event, KeyCode};
3use std::borrow::BorrowMut;
4use std::cmp::{max, min};
5use std::str::Lines;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::mpsc::{RecvError, TryRecvError};
8use std::sync::{mpsc, Arc};
9use std::thread;
10use std::time::Duration;
11use tui::backend::Backend;
12use tui::buffer::Buffer;
13use tui::layout::Rect;
14use tui::style::Style;
15use tui::widgets::{StatefulWidget, Widget};
16use tui::Frame;
17
18pub struct Events {
20 rx: mpsc::Receiver<Event>,
21 ignore_exit_key: Arc<AtomicBool>,
22}
23
24#[derive(Default, Clone)]
26pub struct CommandInput {
27 prompt: String,
28}
29
30#[derive(Default)]
31pub struct CommandInputState {
32 history: Vec<String>,
33 index_of_history: usize,
34 content: String,
35}
36
37#[derive(Default, Clone)]
38pub struct CommandOutput {}
39
40#[derive(Default)]
41pub struct CommandOutputState {
42 history: Vec<String>,
43}
44
45impl CommandInputState {
46 pub fn add_char(&mut self, c: char) {
47 self.content.push(c);
48 }
49
50 pub fn del_char(&mut self) {
51 self.content.pop();
52 }
53
54 pub fn reset(&mut self) {
55 self.content.drain(..);
56 }
57
58 pub fn enter(&mut self) -> String {
59 let command = self.content.clone();
60 self.history.push(command.clone());
61 self.reset();
62
63 command
64 }
65
66 pub fn back_in_history(&mut self) {
67 if self.history.is_empty() {
68 return;
69 }
70
71 self.index_of_history = min(self.index_of_history + 1, self.history.len() - 1);
72
73 self.content = self.history[self.index_of_history].clone();
74 }
75
76 pub fn forward_in_history(&mut self) {
77 if self.history.is_empty() {
78 return;
79 }
80
81 self.index_of_history = max(self.index_of_history - 1, 0);
82
83 self.content = self.history[self.index_of_history].clone();
84 }
85}
86
87impl CommandInput {
88 pub fn prompt(&mut self, prompt: &str) {
89 self.prompt = prompt.to_string();
90 }
91}
92
93impl StatefulWidget for CommandInput {
94 type State = CommandInputState;
95
96 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
97 buf.set_string(area.left(), area.top(), &self.prompt, Style::default());
98 buf.set_string(
99 area.left() + self.prompt.len() as u16,
100 area.top(),
101 &state.content,
102 Style::default(),
103 );
104 }
105}
106
107impl Widget for CommandInput {
108 fn render(self, area: Rect, buf: &mut Buffer) {
109 StatefulWidget::render(self, area, buf, &mut CommandInputState::default())
110 }
111}
112
113impl StatefulWidget for CommandOutput {
114 type State = CommandOutputState;
115
116 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
117 let max_lines = area.height - 1;
118 let max_chars_per_line = area.width - 1;
119
120 let mut lines_to_render: Vec<&str> = vec![];
121
122 let history_to_show = state.history.iter().rev().take(max_lines as usize).rev();
123 let mut y = 0;
124 for line in history_to_show {
125 if line.len() > max_chars_per_line as usize {
126 let mut rest_of_line = line.as_str();
127 loop {
128 if rest_of_line.len() > max_chars_per_line as usize {
129 let split_line = rest_of_line.split_at(max_chars_per_line as usize);
130 lines_to_render.push(split_line.0);
131 rest_of_line = split_line.1;
132 } else {
133 lines_to_render.push(rest_of_line);
134 break;
135 }
136 }
137 } else {
138 lines_to_render.push(line);
139 }
140 }
141
142 for line in lines_to_render.iter().rev().take(max_lines as usize).rev() {
143 buf.set_string(area.left(), area.top() + y, line, Style::default());
144 y += 1;
145 }
146 }
147}
148
149impl Widget for CommandOutput {
150 fn render(self, area: Rect, buf: &mut Buffer) {
151 StatefulWidget::render(self, area, buf, &mut CommandOutputState::default())
152 }
153}
154
155#[derive(Debug, Clone, Copy)]
156pub struct Config {
157 pub exit_key: KeyCode,
158 pub tick_rate: Duration,
159}
160
161impl Default for Config {
162 fn default() -> Config {
163 Config {
164 exit_key: KeyCode::Char('q'),
165 tick_rate: Duration::from_millis(250),
166 }
167 }
168}
169
170impl Default for Events {
171 fn default() -> Self {
172 Events::from_config(Config::default())
173 }
174}
175
176impl Events {
177 pub fn from_config(config: Config) -> Events {
179 let (tx, rx) = mpsc::channel();
180 let ignore_exit_key = Arc::new(AtomicBool::new(false));
181 {
182 let ignore_exit_key = ignore_exit_key.clone();
183 thread::spawn(move || loop {
184 if let Ok(b) = poll(config.tick_rate) {
185 if !b {
186 continue;
187 }
188 let read = read();
189 if let Ok(event) = read {
190 if let Err(err) = tx.send(event) {
191 eprintln!("{}", err);
192 return;
193 }
194 if !ignore_exit_key.load(Ordering::Relaxed) {
195 if let Event::Key(key) = event {
196 if key.code == config.exit_key {
197 return;
198 }
199 }
200 }
201 }
202 }
203 })
204 };
205 Events {
206 rx,
207 ignore_exit_key,
208 }
209 }
210
211 pub fn next(&self) -> Result<Option<Event>, mpsc::RecvError> {
214 match self.rx.try_recv() {
215 Ok(event) => Ok(Some(event)),
216 Err(err) => match err {
217 TryRecvError::Empty => Ok(None),
218 TryRecvError::Disconnected => Err(RecvError {}),
219 },
220 }
221 }
222
223 pub fn disable_exit_key(&mut self) {
224 self.ignore_exit_key.store(true, Ordering::Relaxed);
225 }
226
227 pub fn enable_exit_key(&mut self) {
228 self.ignore_exit_key.store(false, Ordering::Relaxed);
229 }
230}
231
232pub struct TuiClap<'a> {
234 command_input_state: CommandInputState,
235 command_output_state: CommandOutputState,
236 command_input_widget: CommandInput,
237 command_output_widget: CommandOutput,
238 clap: App<'a>
239}
240
241impl TuiClap<'_> {
242 pub fn from_app<'a>(
244 app: App<'a>,
245 ) -> TuiClap {
246 TuiClap {
247 command_input_state: CommandInputState::default(),
248 command_output_state: CommandOutputState::default(),
249 command_input_widget: Default::default(),
250 command_output_widget: Default::default(),
251 clap: app,
252 }
253 }
254
255 pub fn write_to_output(&mut self, string: String) {
257 let lines: Lines = string.lines();
258 for str in lines {
259 self.command_output_state.history.push(str.to_string());
260 }
261 }
262
263 pub fn state(&mut self) -> &mut CommandInputState {
265 self.command_input_state.borrow_mut()
266 }
267
268 pub fn parse(&mut self) -> Result<ArgMatches, ()> {
271 let content = self.command_input_state.content.clone();
272 self.state().enter();
273
274 let commands_vec = content.split(' ').collect::<Vec<&str>>();
275 let matches_result = self.clap.try_get_matches_from_mut(commands_vec.clone());
276
277 match matches_result {
278 Ok(matches) => Ok(matches),
279 Err(err) => match err.kind {
280 ErrorKind::DisplayHelp => {
281 let mut buf = Vec::new();
282 let mut writer = Box::new(&mut buf);
283 self.clap
284 .write_help(&mut writer)
285 .expect("Could not write help");
286 self.write_to_output(std::str::from_utf8(buf.as_slice()).unwrap().to_string());
287 Err(())
288 }
289 ErrorKind::DisplayVersion => {
290 self.write_to_output(self.clap.render_long_version());
291 Err(())
292 }
293 ErrorKind::Format => {
294 Err(())
295 }
296 _ => {
297 self.write_to_output(format!("error: {}", err));
298 Err(())
299 },
300 },
301 }
302 }
303
304 pub fn input_widget(&mut self) -> &mut CommandInput {
306 self.command_input_widget.borrow_mut()
307 }
308
309 pub fn render_input<B: Backend>(&mut self, frame: &mut Frame<B>, area: Rect) {
311 frame.render_stateful_widget(
312 self.command_input_widget.clone(),
313 area,
314 self.command_input_state.borrow_mut(),
315 );
316 }
317
318 pub fn output_widget(&mut self) -> &mut CommandOutput {
320 self.command_output_widget.borrow_mut()
321 }
322
323 pub fn render_output<B: Backend>(&mut self, frame: &mut Frame<B>, area: Rect) {
325 frame.render_stateful_widget(
326 self.command_output_widget.clone(),
327 area,
328 self.command_output_state.borrow_mut(),
329 );
330 }
331}