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, 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> 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}