1use std::{
2 cmp::Ordering,
3 io::{self, Write},
4};
5
6use crossterm::{
7 cursor, queue,
8 style::{
9 Attribute as CAttribute, Color as CColor, SetAttribute, SetBackgroundColor,
10 SetForegroundColor,
11 },
12 terminal,
13};
14
15use super::{Attributes, Backend, ClearType, Color, MoveDirection, Size};
16
17#[derive(Debug, Clone)]
19#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
20pub struct CrosstermBackend<W> {
21 buffer: W,
22 attributes: Attributes,
23}
24
25impl<W> CrosstermBackend<W> {
26 pub fn new(buffer: W) -> CrosstermBackend<W> {
28 CrosstermBackend {
29 buffer,
30 attributes: Attributes::empty(),
31 }
32 }
33}
34
35impl<W: Write> Write for CrosstermBackend<W> {
36 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
37 self.buffer.write(buf)
38 }
39
40 fn flush(&mut self) -> io::Result<()> {
41 self.buffer.flush()
42 }
43}
44
45impl<W: Write> Backend for CrosstermBackend<W> {
46 fn enable_raw_mode(&mut self) -> io::Result<()> {
47 terminal::enable_raw_mode()
48 }
49
50 fn disable_raw_mode(&mut self) -> io::Result<()> {
51 terminal::disable_raw_mode()
52 }
53
54 fn hide_cursor(&mut self) -> io::Result<()> {
55 queue!(self.buffer, cursor::Hide)
56 }
57
58 fn show_cursor(&mut self) -> io::Result<()> {
59 queue!(self.buffer, cursor::Show)
60 }
61
62 fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
63 cursor::position()
64 }
65
66 fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
67 queue!(self.buffer, cursor::MoveTo(x, y))
68 }
69
70 fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
71 match direction {
72 MoveDirection::Up(n) => queue!(self.buffer, cursor::MoveUp(n)),
73 MoveDirection::Down(n) => queue!(self.buffer, cursor::MoveDown(n)),
74 MoveDirection::Left(n) => queue!(self.buffer, cursor::MoveLeft(n)),
75 MoveDirection::Right(n) => queue!(self.buffer, cursor::MoveRight(n)),
76 MoveDirection::NextLine(n) => {
77 queue!(self.buffer, cursor::MoveToNextLine(n))
78 }
79 MoveDirection::Column(n) => queue!(self.buffer, cursor::MoveToColumn(n)),
80 MoveDirection::PrevLine(n) => {
81 queue!(self.buffer, cursor::MoveToPreviousLine(n))
82 }
83 }
84 }
85
86 fn scroll(&mut self, dist: i16) -> io::Result<()> {
87 match dist.cmp(&0) {
88 Ordering::Greater => {
89 queue!(self.buffer, terminal::ScrollDown(dist as u16))
90 }
91 Ordering::Less => {
92 queue!(self.buffer, terminal::ScrollUp(-dist as u16))
93 }
94 Ordering::Equal => Ok(()),
95 }
96 }
97
98 fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
99 set_attributes(self.attributes, attributes, &mut self.buffer)?;
100 self.attributes = attributes;
101 Ok(())
102 }
103
104 fn set_fg(&mut self, color: Color) -> io::Result<()> {
105 queue!(self.buffer, SetForegroundColor(color.into()))
106 }
107
108 fn set_bg(&mut self, color: Color) -> io::Result<()> {
109 queue!(self.buffer, SetBackgroundColor(color.into()))
110 }
111
112 fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
113 queue!(self.buffer, terminal::Clear(clear_type.into()))
114 }
115
116 fn size(&self) -> io::Result<Size> {
117 terminal::size().map(Into::into)
118 }
119}
120
121impl From<Color> for CColor {
122 fn from(color: Color) -> Self {
123 match color {
124 Color::Reset => CColor::Reset,
125 Color::Black => CColor::Black,
126 Color::Red => CColor::DarkRed,
127 Color::Green => CColor::DarkGreen,
128 Color::Yellow => CColor::DarkYellow,
129 Color::Blue => CColor::DarkBlue,
130 Color::Magenta => CColor::DarkMagenta,
131 Color::Cyan => CColor::DarkCyan,
132 Color::Grey => CColor::Grey,
133 Color::DarkGrey => CColor::DarkGrey,
134 Color::LightRed => CColor::Red,
135 Color::LightGreen => CColor::Green,
136 Color::LightBlue => CColor::Blue,
137 Color::LightYellow => CColor::Yellow,
138 Color::LightMagenta => CColor::Magenta,
139 Color::LightCyan => CColor::Cyan,
140 Color::White => CColor::White,
141 Color::Ansi(i) => CColor::AnsiValue(i),
142 Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
143 }
144 }
145}
146
147impl From<ClearType> for terminal::ClearType {
148 fn from(clear: ClearType) -> Self {
149 match clear {
150 ClearType::All => terminal::ClearType::All,
151 ClearType::FromCursorDown => terminal::ClearType::FromCursorDown,
152 ClearType::FromCursorUp => terminal::ClearType::FromCursorUp,
153 ClearType::CurrentLine => terminal::ClearType::CurrentLine,
154 ClearType::UntilNewLine => terminal::ClearType::UntilNewLine,
155 }
156 }
157}
158
159pub(super) fn set_attributes<W: Write>(
160 from: Attributes,
161 to: Attributes,
162 mut w: W,
163) -> io::Result<()> {
164 let diff = from.diff(to);
165 if diff.to_remove.contains(Attributes::REVERSED) {
166 queue!(w, SetAttribute(CAttribute::NoReverse))?;
167 }
168 if diff.to_remove.contains(Attributes::BOLD) {
169 queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
170 if to.contains(Attributes::DIM) {
171 queue!(w, SetAttribute(CAttribute::Dim))?;
172 }
173 }
174 if diff.to_remove.contains(Attributes::ITALIC) {
175 queue!(w, SetAttribute(CAttribute::NoItalic))?;
176 }
177 if diff.to_remove.contains(Attributes::UNDERLINED) {
178 queue!(w, SetAttribute(CAttribute::NoUnderline))?;
179 }
180 if diff.to_remove.contains(Attributes::DIM) {
181 queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
182 }
183 if diff.to_remove.contains(Attributes::CROSSED_OUT) {
184 queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
185 }
186 if diff.to_remove.contains(Attributes::SLOW_BLINK)
187 || diff.to_remove.contains(Attributes::RAPID_BLINK)
188 {
189 queue!(w, SetAttribute(CAttribute::NoBlink))?;
190 }
191
192 if diff.to_add.contains(Attributes::REVERSED) {
193 queue!(w, SetAttribute(CAttribute::Reverse))?;
194 }
195 if diff.to_add.contains(Attributes::BOLD) {
196 queue!(w, SetAttribute(CAttribute::Bold))?;
197 }
198 if diff.to_add.contains(Attributes::ITALIC) {
199 queue!(w, SetAttribute(CAttribute::Italic))?;
200 }
201 if diff.to_add.contains(Attributes::UNDERLINED) {
202 queue!(w, SetAttribute(CAttribute::Underlined))?;
203 }
204 if diff.to_add.contains(Attributes::DIM) {
205 queue!(w, SetAttribute(CAttribute::Dim))?;
206 }
207 if diff.to_add.contains(Attributes::CROSSED_OUT) {
208 queue!(w, SetAttribute(CAttribute::CrossedOut))?;
209 }
210 if diff.to_add.contains(Attributes::SLOW_BLINK) {
211 queue!(w, SetAttribute(CAttribute::SlowBlink))?;
212 }
213 if diff.to_add.contains(Attributes::RAPID_BLINK) {
214 queue!(w, SetAttribute(CAttribute::RapidBlink))?;
215 }
216
217 Ok(())
218}