1use crossterm::{cursor as c, event as e, execute, queue, style as s, terminal as t, ErrorKind};
2use std::io::{self, stdout, Stdout, Write};
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum TermError {
7 #[error("Failure to initialize terminal")]
8 Init,
9 #[error("Failure to deinitialize terminal")]
10 DeInit,
11 #[error("Failure to run command: {0:?}")]
12 Command(io::Error),
13}
14impl From<ErrorKind> for TermError {
15 fn from(err: ErrorKind) -> TermError {
16 TermError::Command(err)
17 }
18}
19
20pub struct Rgb {
21 r: u8,
22 g: u8,
23 b: u8,
24}
25impl From<Rgb> for s::Color {
26 fn from(rgb: Rgb) -> s::Color {
27 let Rgb { r, g, b } = rgb;
28 s::Color::Rgb { r, g, b }
29 }
30}
31
32pub enum IEvent {
35 Key(e::KeyEvent),
36 Paste(String),
37 FocusGained,
38 FocusLost,
39 }
43
44#[derive(Clone, Copy)]
45pub struct Size {
46 cols: u16,
47 rows: u16,
48}
49
50pub struct Terminal {
51 size: Size,
52 _stdout: Stdout,
53}
54
55type Result<T> = std::result::Result<T, TermError>;
56
57impl Terminal {
58 pub fn new() -> Result<Self> {
59 let size = t::size()?;
60 Ok(Self {
61 size: Size {
62 cols: size.0,
63 rows: size.1,
64 },
65 _stdout: stdout(),
66 })
67 }
68
69 pub fn flush(&mut self) -> Result<()> {
70 Ok(self._stdout.flush()?)
71 }
72
73 pub fn read_event(&mut self) -> Result<IEvent> {
77 loop {
78 match e::read()? {
79 e::Event::Key(key) => return Ok(IEvent::Key(key)),
80 e::Event::Mouse(mouse) => self.cursor_position(mouse.column, mouse.row)?,
81 e::Event::Resize(x, y) => self.size = Size { cols: x, rows: y },
82 e::Event::Paste(s) => return Ok(IEvent::Paste(s)),
83 e::Event::FocusGained => return Ok(IEvent::FocusGained),
84 e::Event::FocusLost => return Ok(IEvent::FocusLost),
85 }
86 }
87 }
88
89 pub fn cursor_hide(&mut self) -> Result<()> {
91 Ok(queue!(self._stdout, c::Hide)?)
92 }
93 pub fn cursor_show(&mut self) -> Result<()> {
95 Ok(queue!(self._stdout, c::Show)?)
96 }
97
98 pub fn cursor_position(&mut self, col: u16, row: u16) -> Result<()> {
103 Ok(queue!(self._stdout, c::MoveTo(col, row))?)
104 }
105
106 pub fn clear_screen(&mut self) -> Result<()> {
108 Ok(queue!(self._stdout, t::Clear(t::ClearType::All))?)
109 }
110 pub fn clear_line(&mut self) -> Result<()> {
112 Ok(queue!(self._stdout, t::Clear(t::ClearType::CurrentLine))?)
113 }
114
115 pub fn fg_set(&mut self, rgb: Rgb) -> Result<()> {
116 Ok(queue!(self._stdout, s::SetForegroundColor(rgb.into()))?)
117 }
118 pub fn bg_set(&mut self, rgb: Rgb) -> Result<()> {
119 Ok(queue!(self._stdout, s::SetBackgroundColor(rgb.into()))?)
120 }
121 pub fn fg_reset(&mut self) -> Result<()> {
122 Ok(queue!(
123 self._stdout,
124 s::SetForegroundColor(s::Color::Reset)
125 )?)
126 }
127 pub fn bg_reset(&mut self) -> Result<()> {
128 Ok(queue!(
129 self._stdout,
130 s::SetBackgroundColor(s::Color::Reset)
131 )?)
132 }
133
134 pub fn size_get(&self) -> &Size {
135 &self.size
136 }
137 pub fn size_set(&mut self, size: Size) -> Result<()> {
138 self.size = size;
139 Ok(queue!(
140 self._stdout,
141 t::SetSize(self.size.cols, self.size.rows)
142 )?)
143 }
144
145 pub fn init(&mut self) -> Result<()> {
146 execute!(
147 self._stdout,
148 t::EnterAlternateScreen,
149 s::ResetColor,
150 t::Clear(t::ClearType::All),
151 c::MoveTo(0, 0),
152 )
153 .map_err(|_| TermError::Init)?;
154
155 t::enable_raw_mode().map_err(|_| TermError::Init)
156 }
157 pub fn deinit(&mut self) -> Result<()> {
158 execute!(
159 self._stdout,
160 s::ResetColor,
161 c::Show,
162 t::LeaveAlternateScreen
163 )
164 .map_err(|_| TermError::DeInit)
165 }
166}