requestty_ui/backend/
termion.rs

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/// A backend that uses the `termion` library.
45#[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    /// Creates a new [`TermionBackend`]
54    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            // 0 index the position
115            .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        // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
250        // terminals, and NoFaint additionally disables bold... so we use this trick to get
251        // the right semantics.
252        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        // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
268        // here if we want it.
269        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}