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