tui_temp_fork/backend/
termion.rs

1use log::debug;
2use std::fmt;
3use std::io;
4use std::io::Write;
5
6use super::Backend;
7use crate::buffer::Cell;
8use crate::layout::Rect;
9use crate::style;
10
11pub struct TermionBackend<W>
12where
13    W: Write,
14{
15    stdout: W,
16}
17
18impl<W> TermionBackend<W>
19where
20    W: Write,
21{
22    pub fn new(stdout: W) -> TermionBackend<W> {
23        TermionBackend { stdout }
24    }
25}
26
27impl<W> Write for TermionBackend<W>
28where
29    W: Write,
30{
31    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
32        self.stdout.write(buf)
33    }
34
35    fn flush(&mut self) -> io::Result<()> {
36        self.stdout.flush()
37    }
38}
39
40impl<W> Backend for TermionBackend<W>
41where
42    W: Write,
43{
44    /// Clears the entire screen and move the cursor to the top left of the screen
45    fn clear(&mut self) -> io::Result<()> {
46        write!(self.stdout, "{}", termion::clear::All)?;
47        write!(self.stdout, "{}", termion::cursor::Goto(1, 1))?;
48        self.stdout.flush()
49    }
50
51    /// Hides cursor
52    fn hide_cursor(&mut self) -> io::Result<()> {
53        write!(self.stdout, "{}", termion::cursor::Hide)?;
54        self.stdout.flush()
55    }
56
57    /// Shows cursor
58    fn show_cursor(&mut self) -> io::Result<()> {
59        write!(self.stdout, "{}", termion::cursor::Show)?;
60        self.stdout.flush()
61    }
62
63    /// Gets cursor position (0-based index)
64    fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
65        termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1))
66    }
67
68    /// Sets cursor position (0-based index)
69    fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
70        write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
71        self.stdout.flush()
72    }
73
74    fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
75    where
76        I: Iterator<Item = (u16, u16, &'a Cell)>,
77    {
78        use std::fmt::Write;
79
80        let mut string = String::with_capacity(content.size_hint().0 * 3);
81        let mut style = style::Style::default();
82        let mut last_y = 0;
83        let mut last_x = 0;
84        let mut inst = 0;
85        for (x, y, cell) in content {
86            if y != last_y || x != last_x + 1 || inst == 0 {
87                write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
88                inst += 1;
89            }
90            last_x = x;
91            last_y = y;
92            if cell.style.modifier != style.modifier {
93                write!(
94                    string,
95                    "{}",
96                    ModifierDiff {
97                        from: style.modifier,
98                        to: cell.style.modifier
99                    }
100                )
101                .unwrap();
102                style.modifier = cell.style.modifier;
103                inst += 1;
104            }
105            if cell.style.fg != style.fg {
106                write!(string, "{}", Fg(cell.style.fg)).unwrap();
107                style.fg = cell.style.fg;
108                inst += 1;
109            }
110            if cell.style.bg != style.bg {
111                write!(string, "{}", Bg(cell.style.bg)).unwrap();
112                style.bg = cell.style.bg;
113                inst += 1;
114            }
115            string.push_str(&cell.symbol);
116            inst += 1;
117        }
118        debug!("{} instructions outputed.", inst);
119        write!(
120            self.stdout,
121            "{}{}{}{}",
122            string,
123            Fg(style::Color::Reset),
124            Bg(style::Color::Reset),
125            termion::style::Reset,
126        )
127    }
128
129    /// Return the size of the terminal
130    fn size(&self) -> io::Result<Rect> {
131        let terminal = termion::terminal_size()?;
132        Ok(Rect::new(0, 0, terminal.0, terminal.1))
133    }
134
135    fn flush(&mut self) -> io::Result<()> {
136        self.stdout.flush()
137    }
138}
139
140struct Fg(style::Color);
141
142struct Bg(style::Color);
143
144struct ModifierDiff {
145    from: style::Modifier,
146    to: style::Modifier,
147}
148
149impl fmt::Display for Fg {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        use termion::color::Color;
152        match self.0 {
153            style::Color::Reset => termion::color::Reset.write_fg(f),
154            style::Color::Black => termion::color::Black.write_fg(f),
155            style::Color::Red => termion::color::Red.write_fg(f),
156            style::Color::Green => termion::color::Green.write_fg(f),
157            style::Color::Yellow => termion::color::Yellow.write_fg(f),
158            style::Color::Blue => termion::color::Blue.write_fg(f),
159            style::Color::Magenta => termion::color::Magenta.write_fg(f),
160            style::Color::Cyan => termion::color::Cyan.write_fg(f),
161            style::Color::Gray => termion::color::White.write_fg(f),
162            style::Color::DarkGray => termion::color::LightBlack.write_fg(f),
163            style::Color::LightRed => termion::color::LightRed.write_fg(f),
164            style::Color::LightGreen => termion::color::LightGreen.write_fg(f),
165            style::Color::LightBlue => termion::color::LightBlue.write_fg(f),
166            style::Color::LightYellow => termion::color::LightYellow.write_fg(f),
167            style::Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
168            style::Color::LightCyan => termion::color::LightCyan.write_fg(f),
169            style::Color::White => termion::color::LightWhite.write_fg(f),
170            style::Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
171            style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
172        }
173    }
174}
175impl fmt::Display for Bg {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        use termion::color::Color;
178        match self.0 {
179            style::Color::Reset => termion::color::Reset.write_bg(f),
180            style::Color::Black => termion::color::Black.write_bg(f),
181            style::Color::Red => termion::color::Red.write_bg(f),
182            style::Color::Green => termion::color::Green.write_bg(f),
183            style::Color::Yellow => termion::color::Yellow.write_bg(f),
184            style::Color::Blue => termion::color::Blue.write_bg(f),
185            style::Color::Magenta => termion::color::Magenta.write_bg(f),
186            style::Color::Cyan => termion::color::Cyan.write_bg(f),
187            style::Color::Gray => termion::color::White.write_bg(f),
188            style::Color::DarkGray => termion::color::LightBlack.write_bg(f),
189            style::Color::LightRed => termion::color::LightRed.write_bg(f),
190            style::Color::LightGreen => termion::color::LightGreen.write_bg(f),
191            style::Color::LightBlue => termion::color::LightBlue.write_bg(f),
192            style::Color::LightYellow => termion::color::LightYellow.write_bg(f),
193            style::Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
194            style::Color::LightCyan => termion::color::LightCyan.write_bg(f),
195            style::Color::White => termion::color::LightWhite.write_bg(f),
196            style::Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
197            style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
198        }
199    }
200}
201
202impl fmt::Display for ModifierDiff {
203    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204        let remove = self.from - self.to;
205        if remove.contains(style::Modifier::REVERSED) {
206            write!(f, "{}", termion::style::NoInvert)?;
207        }
208        if remove.contains(style::Modifier::BOLD) {
209            // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
210            // terminals, and NoFaint additionally disables bold... so we use this trick to get
211            // the right semantics.
212            write!(f, "{}", termion::style::NoFaint)?;
213
214            if self.to.contains(style::Modifier::DIM) {
215                write!(f, "{}", termion::style::Faint)?;
216            }
217        }
218        if remove.contains(style::Modifier::ITALIC) {
219            write!(f, "{}", termion::style::NoItalic)?;
220        }
221        if remove.contains(style::Modifier::UNDERLINED) {
222            write!(f, "{}", termion::style::NoUnderline)?;
223        }
224        if remove.contains(style::Modifier::DIM) {
225            write!(f, "{}", termion::style::NoFaint)?;
226
227            // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
228            // here if we want it.
229            if self.to.contains(style::Modifier::BOLD) {
230                write!(f, "{}", termion::style::Bold)?;
231            }
232        }
233        if remove.contains(style::Modifier::CROSSED_OUT) {
234            write!(f, "{}", termion::style::NoCrossedOut)?;
235        }
236        if remove.contains(style::Modifier::SLOW_BLINK)
237            || remove.contains(style::Modifier::RAPID_BLINK)
238        {
239            write!(f, "{}", termion::style::NoBlink)?;
240        }
241
242        let add = self.to - self.from;
243        if add.contains(style::Modifier::REVERSED) {
244            write!(f, "{}", termion::style::Invert)?;
245        }
246        if add.contains(style::Modifier::BOLD) {
247            write!(f, "{}", termion::style::Bold)?;
248        }
249        if add.contains(style::Modifier::ITALIC) {
250            write!(f, "{}", termion::style::Italic)?;
251        }
252        if add.contains(style::Modifier::UNDERLINED) {
253            write!(f, "{}", termion::style::Underline)?;
254        }
255        if add.contains(style::Modifier::DIM) {
256            write!(f, "{}", termion::style::Faint)?;
257        }
258        if add.contains(style::Modifier::CROSSED_OUT) {
259            write!(f, "{}", termion::style::CrossedOut)?;
260        }
261        if add.contains(style::Modifier::SLOW_BLINK) || add.contains(style::Modifier::RAPID_BLINK) {
262            write!(f, "{}", termion::style::Blink)?;
263        }
264
265        Ok(())
266    }
267}