requestty_ui/backend/
crossterm.rs

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