1use std::io::{Result, Write};
2
3use crossterm::{
4 cursor::{position, MoveTo, MoveToPreviousLine},
5 event::{read, Event},
6 execute, queue,
7 style::{Attribute, Attributes, Color as Cc, Colors, Print, SetAttributes, SetColors},
8 terminal::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, Clear, ClearType},
9};
10
11use crate::{
12 input::Key,
13 style::{Color, Formatting, FormattingOption}
14};
15
16use super::{CommandBuffer, Engine};
17
18struct RawMode(bool);
19
20pub struct CrosstermEngine<W: Write> {
22 buffer: W,
23 raw_mode: RawMode,
24 previous_line_count: u16,
25}
26
27pub struct CrosstermCommandBuffer<W: Write> {
29 commands: Vec<Box<dyn Command<W>>>,
30 lines_count: u16,
31}
32
33impl<W: Write> CrosstermEngine<W> {
34 pub fn new(buffer: W) -> Self {
35 CrosstermEngine {
36 buffer,
37 raw_mode: RawMode::ensure(),
38 previous_line_count: 1,
39 }
40 }
41}
42
43impl<W: Write> Engine for CrosstermEngine<W> {
44 type Buffer = CrosstermCommandBuffer<W>;
45
46 fn get_command_buffer(&self) -> Self::Buffer {
47 CrosstermCommandBuffer::new()
48 }
49
50 fn render(&mut self, render_commands: &Self::Buffer) -> Result<()> {
51 for _ in 0..self.previous_line_count - 1 {
52 queue!(self.buffer, MoveToPreviousLine(1))?;
53 }
54
55 queue!(self.buffer, MoveTo(0, position()?.1))?;
56
57 for cmd in &render_commands.commands {
58 cmd.execute(&mut self.buffer)?;
59 }
60
61 queue!(self.buffer, Clear(ClearType::FromCursorDown))?;
62
63 self.previous_line_count = render_commands.lines_count;
64 self.buffer.flush()
65 }
66
67 fn finish_rendering(&mut self) -> Result<()> {
68 execute!(self.buffer, Print("\r\n"))
69 }
70
71 fn read_key(&self) -> Result<Key> {
72 loop {
73 match read() {
74 Ok(evt) => {
75 if let Event::Key(key) = evt {
76 return Ok(key.code.into());
77 } else {
78 continue;
79 }
80 }
81 Err(error) => return Err(error),
82 }
83 }
84 }
85}
86
87impl<W: Write> CrosstermCommandBuffer<W> {
88 fn new() -> Self {
89 CrosstermCommandBuffer {
90 commands: vec![],
91 lines_count: 1,
92 }
93 }
94}
95
96impl<W: Write> CommandBuffer for CrosstermCommandBuffer<W> {
97 fn new_line(&mut self) {
98 self.commands.push(Box::new(NewLineCommand));
99 self.lines_count += 1;
100 }
101
102 fn print(&mut self, text: &str) {
103 self.commands.push(Box::new(PrintCommand(text.to_owned())));
104 }
105
106 fn set_formatting(&mut self, formatting: &Formatting) {
107 self.commands
108 .push(Box::new(SetFormattingCommand(formatting.to_owned())));
109 }
110
111 fn reset_formatting(&mut self) {
112 self.commands
113 .push(Box::new(SetFormattingCommand(Formatting::reset())));
114 }
115}
116
117impl<W: Write> super::Clear for CrosstermCommandBuffer<W> {
118 fn clear(&mut self) {
119 self.commands.clear();
120 self.lines_count = 1;
121 }
122}
123
124impl RawMode {
125 pub fn ensure() -> Self {
126 let is_raw = is_raw_mode_enabled().unwrap_or(false);
127 if !is_raw {
128 enable_raw_mode().unwrap_or_default();
129 }
130
131 Self(is_raw)
132 }
133}
134
135impl Drop for RawMode {
136 fn drop(&mut self) {
137 if !self.0 {
138 disable_raw_mode().unwrap_or_default();
139 }
140 }
141}
142
143struct NewLineCommand;
144struct PrintCommand(String);
145struct SetFormattingCommand(Formatting);
146
147trait Command<W: Write> {
148 fn execute(&self, buffer: &mut W) -> Result<()>;
149}
150
151impl<W: Write> Command<W> for PrintCommand {
152 fn execute(&self, buffer: &mut W) -> Result<()> {
153 queue!(buffer, Print(&self.0), Clear(ClearType::UntilNewLine))
154 }
155}
156
157impl<W: Write> Command<W> for NewLineCommand {
158 fn execute(&self, buffer: &mut W) -> Result<()> {
159 queue!(buffer, Print("\r\n"))
160 }
161}
162
163impl<W: Write> Command<W> for SetFormattingCommand {
164 fn execute(&self, buffer: &mut W) -> Result<()> {
165 let colors = Colors {
166 foreground: self.0.foreground_color.map(|c| c.into()),
167 background: self.0.background_color.map(|c| c.into()),
168 };
169
170 let attributes_vec: Vec<Attribute> =
171 self.0.text_formatting.iter().map(|&f| f.into()).collect();
172 let attributes_ref: &[Attribute] = &attributes_vec;
173 let attributes: Attributes = attributes_ref.into();
174
175 queue!(buffer, SetColors(colors), SetAttributes(attributes))
176 }
177}
178
179impl From<FormattingOption> for crossterm::style::Attribute {
180 fn from(value: FormattingOption) -> Self {
181 match value {
182 FormattingOption::Reset => Attribute::Reset,
183 FormattingOption::Bold => Attribute::Bold,
184 FormattingOption::Italic => Attribute::Italic,
185 FormattingOption::Underline => Attribute::Underlined,
186 FormattingOption::CrossedOut => Attribute::CrossedOut,
187 }
188 }
189}
190
191impl From<crossterm::event::KeyCode> for Key {
192 fn from(key_code: crossterm::event::KeyCode) -> Self {
193 match key_code {
194 crossterm::event::KeyCode::Backspace => Key::Backspace,
195 crossterm::event::KeyCode::Enter => Key::Enter,
196 crossterm::event::KeyCode::Left => Key::Left,
197 crossterm::event::KeyCode::Right => Key::Right,
198 crossterm::event::KeyCode::Up => Key::Up,
199 crossterm::event::KeyCode::Down => Key::Down,
200 crossterm::event::KeyCode::Home => Key::Home,
201 crossterm::event::KeyCode::End => Key::End,
202 crossterm::event::KeyCode::PageUp => Key::PageUp,
203 crossterm::event::KeyCode::PageDown => Key::PageDown,
204 crossterm::event::KeyCode::Tab => Key::Tab,
205 crossterm::event::KeyCode::BackTab => Key::BackTab,
206 crossterm::event::KeyCode::Delete => Key::Delete,
207 crossterm::event::KeyCode::Insert => Key::Insert,
208 crossterm::event::KeyCode::F(func) => Key::F(func),
209 crossterm::event::KeyCode::Char(c) => Key::Char(c),
210 crossterm::event::KeyCode::Null => Key::Esc,
211 crossterm::event::KeyCode::Esc => Key::Esc,
212 }
213 }
214}
215
216impl From<Color> for Cc {
217 fn from(value: Color) -> Self {
218 match value {
219 Color::Reset => Cc::Reset,
220 Color::Black => Cc::Black,
221 Color::DarkGrey => Cc::DarkGrey,
222 Color::Red => Cc::Red,
223 Color::DarkRed => Cc::DarkRed,
224 Color::Green => Cc::Green,
225 Color::DarkGreen => Cc::DarkGreen,
226 Color::Yellow => Cc::Yellow,
227 Color::DarkYellow => Cc::DarkYellow,
228 Color::Blue => Cc::Blue,
229 Color::DarkBlue => Cc::DarkBlue,
230 Color::Magenta => Cc::Magenta,
231 Color::DarkMagenta => Cc::DarkMagenta,
232 Color::Cyan => Cc::Cyan,
233 Color::DarkCyan => Cc::DarkCyan,
234 Color::White => Cc::White,
235 Color::Grey => Cc::Grey,
236 Color::Rgb { r, g, b } => Cc::Rgb { r, g, b },
237 Color::AnsiValue(c) => Cc::AnsiValue(c),
238 }
239 }
240}