workflow_terminal/terminal/
crossterm.rs

1use crate::keys::Key;
2use crate::terminal::Options;
3use crate::terminal::Terminal;
4use crate::Result;
5use crossterm::event::KeyEventKind;
6use crossterm::event::KeyModifiers;
7pub use crossterm::terminal::disable_raw_mode;
8use crossterm::{
9    event::{self, Event, KeyCode},
10    terminal,
11};
12use std::io::{stdout, Stdout, Write};
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Mutex};
15
16///
17/// # Crossterm
18///
19/// Wrapper around Crossterm interface - [https://crates.io/crates/crossterm](https://crates.io/crates/crossterm)
20///
21pub struct Crossterm {
22    terminal: Arc<Mutex<Option<Arc<Terminal>>>>,
23    terminate: Arc<AtomicBool>,
24    stdout: Arc<Mutex<Option<Stdout>>>,
25}
26
27impl Crossterm {
28    pub fn try_new() -> Result<Self> {
29        Self::try_new_with_options(&Options::default())
30    }
31    pub fn try_new_with_options(_options: &Options) -> Result<Self> {
32        let crossterm = Crossterm {
33            terminal: Arc::new(Mutex::new(None)),
34            terminate: Arc::new(AtomicBool::new(false)),
35            stdout: Arc::new(Mutex::new(Some(stdout()))),
36            // stdout: Arc::new(Mutex::new(Some(stdout().into_raw_mode().unwrap()))),
37        };
38        Ok(crossterm)
39    }
40
41    pub async fn init(self: &Arc<Self>, terminal: &Arc<Terminal>) -> Result<()> {
42        *self.terminal.lock().unwrap() = Some(terminal.clone());
43        Ok(())
44    }
45
46    pub fn exit(&self) {
47        self.terminate.store(true, Ordering::SeqCst);
48    }
49
50    pub fn terminal(&self) -> Arc<Terminal> {
51        self.terminal.lock().unwrap().as_ref().unwrap().clone()
52    }
53
54    pub async fn run(&self) -> Result<()> {
55        terminal::enable_raw_mode()?;
56        self.flush();
57        self.intake(&self.terminate).await?;
58        self.flush();
59        terminal::disable_raw_mode()?;
60
61        Ok(())
62    }
63
64    pub async fn intake(&self, terminate: &Arc<AtomicBool>) -> Result<()> {
65        loop {
66            let event = event::read()?;
67            // println!("{:?}",event);
68            if let Event::Key(key) = event {
69                if matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
70                    let key = match key.code {
71                        KeyCode::Char(c) => {
72                            if key.modifiers & KeyModifiers::ALT == KeyModifiers::ALT {
73                                Key::Alt(c)
74                            } else if key.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL
75                            {
76                                Key::Ctrl(c)
77                            } else {
78                                Key::Char(c)
79                            }
80                        }
81                        KeyCode::Enter => Key::Enter,
82                        KeyCode::Esc => Key::Esc,
83                        KeyCode::Left => Key::ArrowLeft,
84                        KeyCode::Right => Key::ArrowRight,
85                        KeyCode::Up => Key::ArrowUp,
86                        KeyCode::Down => Key::ArrowDown,
87                        KeyCode::Backspace => Key::Backspace,
88                        _ => {
89                            continue;
90                        }
91                    };
92
93                    self.terminal().ingest(key, "".to_string()).await?;
94                    self.flush();
95
96                    if terminate.load(Ordering::SeqCst) {
97                        break;
98                    }
99                }
100            }
101        }
102
103        Ok(())
104    }
105
106    pub fn write<S>(&self, s: S)
107    where
108        S: ToString,
109    {
110        print!("{}", s.to_string());
111        self.flush();
112    }
113
114    pub fn flush(&self) {
115        // stdout
116        if let Some(stdout) = self.stdout.lock().unwrap().as_mut() {
117            stdout.flush().unwrap();
118        }
119    }
120}
121
122use std::{panic, process};
123
124/// configure custom panic hook that disables terminal raw mode
125/// supply a closure that will be called when a panic occurs
126/// giving an opportunity to output a custom message.  The closure
127/// should return a desirable process exit code.
128pub fn init_panic_hook<F>(f: F)
129where
130    F: Fn() -> i32 + Send + Sync + 'static,
131{
132    let default_hook = panic::take_hook();
133    panic::set_hook(Box::new(move |panic_info| {
134        disable_raw_mode().ok();
135        default_hook(panic_info);
136        let exit_code = f();
137        process::exit(exit_code);
138    }));
139}
140
141// compatibility functions
142impl Crossterm {
143    pub fn get_font_size(&self) -> Result<Option<f64>> {
144        Ok(None)
145    }
146
147    pub fn set_font_size(&self, _font_size: f64) -> Result<()> {
148        Ok(())
149    }
150
151    pub fn cols(&self) -> Option<usize> {
152        None
153    }
154
155    pub fn rows(&self) -> Option<usize> {
156        None
157    }
158
159    pub fn increase_font_size(&self) -> Result<Option<f64>> {
160        Ok(None)
161    }
162
163    pub fn decrease_font_size(&self) -> Result<Option<f64>> {
164        Ok(None)
165    }
166}