workflow_terminal/terminal/
crossterm.rs1use 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
16pub 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 };
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 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 if let Some(stdout) = self.stdout.lock().unwrap().as_mut() {
117 stdout.flush().unwrap();
118 }
119 }
120}
121
122use std::{panic, process};
123
124pub 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
141impl 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}