1use std::{
2 cmp::Ordering,
3 fmt,
4 io::{self, Write},
5 ops::{Deref, DerefMut},
6};
7
8use termion::{
9 clear, color, cursor,
10 raw::{IntoRawMode, RawTerminal},
11 scroll, style,
12};
13
14use super::{Attributes, Backend, ClearType, Color, MoveDirection, Size};
15
16enum Terminal<W: Write> {
17 Raw(RawTerminal<W>),
18 Normal(W),
19 TemporaryNone,
20}
21
22impl<W: Write> Deref for Terminal<W> {
23 type Target = W;
24
25 fn deref(&self) -> &Self::Target {
26 match self {
27 Terminal::Raw(w) => w,
28 Terminal::Normal(w) => w,
29 Terminal::TemporaryNone => unreachable!("TemporaryNone is only used during swap"),
30 }
31 }
32}
33
34impl<W: Write> DerefMut for Terminal<W> {
35 fn deref_mut(&mut self) -> &mut Self::Target {
36 match self {
37 Terminal::Raw(w) => w,
38 Terminal::Normal(w) => w,
39 Terminal::TemporaryNone => unreachable!("TemporaryNone is only used during swap"),
40 }
41 }
42}
43
44#[allow(missing_debug_implementations)]
46#[cfg_attr(docsrs, doc(cfg(feature = "termion")))]
47pub struct TermionBackend<W: Write> {
48 attributes: Attributes,
49 buffer: Terminal<W>,
50}
51
52impl<W: Write> TermionBackend<W> {
53 pub fn new(buffer: W) -> TermionBackend<W> {
55 TermionBackend {
56 buffer: Terminal::Normal(buffer),
57 attributes: Attributes::empty(),
58 }
59 }
60}
61
62impl<W: Write> Write for TermionBackend<W> {
63 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
64 self.buffer.write(buf)
65 }
66
67 fn flush(&mut self) -> io::Result<()> {
68 self.buffer.flush()
69 }
70}
71
72impl<W: Write> Backend for TermionBackend<W> {
73 fn enable_raw_mode(&mut self) -> io::Result<()> {
74 match self.buffer {
75 Terminal::Raw(ref mut buf) => buf.activate_raw_mode(),
76 Terminal::Normal(_) => {
77 let buf = match std::mem::replace(&mut self.buffer, Terminal::TemporaryNone) {
78 Terminal::Normal(buf) => buf,
79 _ => unreachable!(),
80 };
81
82 self.buffer = Terminal::Raw(buf.into_raw_mode()?);
83
84 Ok(())
85 }
86 Terminal::TemporaryNone => unreachable!("TemporaryNone is only used during swap"),
87 }
88 }
89
90 fn disable_raw_mode(&mut self) -> io::Result<()> {
91 match self.buffer {
92 Terminal::Raw(ref buf) => buf.suspend_raw_mode(),
93 Terminal::Normal(_) => {
94 if cfg!(debug_assertions) {
95 panic!("Called disable_raw_mode without enable_raw_mode");
96 }
97
98 Ok(())
99 }
100 Terminal::TemporaryNone => unreachable!("TemporaryNone is only used during swap"),
101 }
102 }
103
104 fn hide_cursor(&mut self) -> io::Result<()> {
105 write!(self.buffer, "{}", cursor::Hide)
106 }
107
108 fn show_cursor(&mut self) -> io::Result<()> {
109 write!(self.buffer, "{}", cursor::Show)
110 }
111
112 fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
113 cursor::DetectCursorPos::cursor_pos(&mut *self.buffer)
114 .map(|(x, y)| (x - 1, y - 1))
116 }
117
118 fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
119 write!(self.buffer, "{}", cursor::Goto(x + 1, y + 1))
120 }
121
122 fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
123 match direction {
124 MoveDirection::Up(n) => write!(self.buffer, "{}", cursor::Up(n))?,
125 MoveDirection::Down(n) => write!(self.buffer, "{}", cursor::Down(n))?,
126 MoveDirection::Left(n) => write!(self.buffer, "{}", cursor::Left(n))?,
127 MoveDirection::Right(n) => write!(self.buffer, "{}", cursor::Right(n))?,
128 _ => super::default_move_cursor(self, direction)?,
129 }
130
131 Ok(())
132 }
133
134 fn scroll(&mut self, dist: i16) -> io::Result<()> {
135 match dist.cmp(&0) {
136 Ordering::Greater => {
137 write!(self.buffer, "{}", scroll::Down(dist as u16))
138 }
139 Ordering::Less => {
140 write!(self.buffer, "{}", scroll::Up(-dist as u16))
141 }
142 Ordering::Equal => Ok(()),
143 }
144 }
145
146 fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
147 set_attributes(self.attributes, attributes, &mut *self.buffer)?;
148 self.attributes = attributes;
149 Ok(())
150 }
151
152 fn set_fg(&mut self, color: Color) -> io::Result<()> {
153 write!(self.buffer, "{}", Fg(color))
154 }
155
156 fn set_bg(&mut self, color: Color) -> io::Result<()> {
157 write!(self.buffer, "{}", Bg(color))
158 }
159
160 fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
161 match clear_type {
162 ClearType::All => write!(self.buffer, "{}", clear::All),
163 ClearType::FromCursorDown => {
164 write!(self.buffer, "{}", clear::AfterCursor)
165 }
166 ClearType::FromCursorUp => {
167 write!(self.buffer, "{}", clear::BeforeCursor)
168 }
169 ClearType::CurrentLine => write!(self.buffer, "{}", clear::CurrentLine),
170 ClearType::UntilNewLine => {
171 write!(self.buffer, "{}", clear::UntilNewline)
172 }
173 }
174 }
175
176 fn size(&self) -> io::Result<Size> {
177 termion::terminal_size().map(Into::into)
178 }
179}
180
181pub(super) struct Fg(pub(super) Color);
182
183pub(super) struct Bg(pub(super) Color);
184
185impl fmt::Display for Fg {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 use color::Color as TermionColor;
188 match self.0 {
189 Color::Reset => color::Reset.write_fg(f),
190 Color::Black => color::Black.write_fg(f),
191 Color::Red => color::Red.write_fg(f),
192 Color::Green => color::Green.write_fg(f),
193 Color::Yellow => color::Yellow.write_fg(f),
194 Color::Blue => color::Blue.write_fg(f),
195 Color::Magenta => color::Magenta.write_fg(f),
196 Color::Cyan => color::Cyan.write_fg(f),
197 Color::Grey => color::White.write_fg(f),
198 Color::DarkGrey => color::LightBlack.write_fg(f),
199 Color::LightRed => color::LightRed.write_fg(f),
200 Color::LightGreen => color::LightGreen.write_fg(f),
201 Color::LightBlue => color::LightBlue.write_fg(f),
202 Color::LightYellow => color::LightYellow.write_fg(f),
203 Color::LightMagenta => color::LightMagenta.write_fg(f),
204 Color::LightCyan => color::LightCyan.write_fg(f),
205 Color::White => color::LightWhite.write_fg(f),
206 Color::Ansi(i) => color::AnsiValue(i).write_fg(f),
207 Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_fg(f),
208 }
209 }
210}
211impl fmt::Display for Bg {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 use color::Color as TermionColor;
214 match self.0 {
215 Color::Reset => color::Reset.write_bg(f),
216 Color::Black => color::Black.write_bg(f),
217 Color::Red => color::Red.write_bg(f),
218 Color::Green => color::Green.write_bg(f),
219 Color::Yellow => color::Yellow.write_bg(f),
220 Color::Blue => color::Blue.write_bg(f),
221 Color::Magenta => color::Magenta.write_bg(f),
222 Color::Cyan => color::Cyan.write_bg(f),
223 Color::Grey => color::White.write_bg(f),
224 Color::DarkGrey => color::LightBlack.write_bg(f),
225 Color::LightRed => color::LightRed.write_bg(f),
226 Color::LightGreen => color::LightGreen.write_bg(f),
227 Color::LightBlue => color::LightBlue.write_bg(f),
228 Color::LightYellow => color::LightYellow.write_bg(f),
229 Color::LightMagenta => color::LightMagenta.write_bg(f),
230 Color::LightCyan => color::LightCyan.write_bg(f),
231 Color::White => color::LightWhite.write_bg(f),
232 Color::Ansi(i) => color::AnsiValue(i).write_bg(f),
233 Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_bg(f),
234 }
235 }
236}
237
238pub(super) fn set_attributes<W: Write>(
239 from: Attributes,
240 to: Attributes,
241 mut w: W,
242) -> io::Result<()> {
243 let diff = from.diff(to);
244
245 if diff.to_remove.contains(Attributes::REVERSED) {
246 write!(w, "{}", style::NoInvert)?;
247 }
248 if diff.to_remove.contains(Attributes::BOLD) {
249 write!(w, "{}", style::NoFaint)?;
253
254 if to.contains(Attributes::DIM) {
255 write!(w, "{}", style::Faint)?;
256 }
257 }
258 if diff.to_remove.contains(Attributes::ITALIC) {
259 write!(w, "{}", style::NoItalic)?;
260 }
261 if diff.to_remove.contains(Attributes::UNDERLINED) {
262 write!(w, "{}", style::NoUnderline)?;
263 }
264 if diff.to_remove.contains(Attributes::DIM) {
265 write!(w, "{}", style::NoFaint)?;
266
267 if to.contains(Attributes::BOLD) {
270 write!(w, "{}", style::Bold)?;
271 }
272 }
273 if diff.to_remove.contains(Attributes::CROSSED_OUT) {
274 write!(w, "{}", style::NoCrossedOut)?;
275 }
276 if diff.to_remove.contains(Attributes::SLOW_BLINK)
277 || diff.to_remove.contains(Attributes::RAPID_BLINK)
278 {
279 write!(w, "{}", style::NoBlink)?;
280 }
281
282 if diff.to_add.contains(Attributes::REVERSED) {
283 write!(w, "{}", style::Invert)?;
284 }
285 if diff.to_add.contains(Attributes::BOLD) {
286 write!(w, "{}", style::Bold)?;
287 }
288 if diff.to_add.contains(Attributes::ITALIC) {
289 write!(w, "{}", style::Italic)?;
290 }
291 if diff.to_add.contains(Attributes::UNDERLINED) {
292 write!(w, "{}", style::Underline)?;
293 }
294 if diff.to_add.contains(Attributes::DIM) {
295 write!(w, "{}", style::Faint)?;
296 }
297 if diff.to_add.contains(Attributes::CROSSED_OUT) {
298 write!(w, "{}", style::CrossedOut)?;
299 }
300 if diff.to_add.contains(Attributes::SLOW_BLINK) || diff.to_add.contains(Attributes::RAPID_BLINK)
301 {
302 write!(w, "{}", style::Blink)?;
303 }
304
305 Ok(())
306}