diff options
Diffstat (limited to 'src/ratatui/backend/termion.rs')
| -rw-r--r-- | src/ratatui/backend/termion.rs | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/src/ratatui/backend/termion.rs b/src/ratatui/backend/termion.rs new file mode 100644 index 00000000..76def792 --- /dev/null +++ b/src/ratatui/backend/termion.rs @@ -0,0 +1,275 @@ +use crate::{ + backend::{Backend, ClearType}, + buffer::Cell, + layout::Rect, + style::{Color, Modifier}, +}; +use std::{ + fmt, + io::{self, Write}, +}; + +pub struct TermionBackend<W> +where + W: Write, +{ + stdout: W, +} + +impl<W> TermionBackend<W> +where + W: Write, +{ + pub fn new(stdout: W) -> TermionBackend<W> { + TermionBackend { stdout } + } +} + +impl<W> Write for TermionBackend<W> +where + W: Write, +{ + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.stdout.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.stdout.flush() + } +} + +impl<W> Backend for TermionBackend<W> +where + W: Write, +{ + fn clear(&mut self) -> io::Result<()> { + self.clear_region(ClearType::All) + } + + fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> { + match clear_type { + ClearType::All => write!(self.stdout, "{}", termion::clear::All)?, + ClearType::AfterCursor => write!(self.stdout, "{}", termion::clear::AfterCursor)?, + ClearType::BeforeCursor => write!(self.stdout, "{}", termion::clear::BeforeCursor)?, + ClearType::CurrentLine => write!(self.stdout, "{}", termion::clear::CurrentLine)?, + ClearType::UntilNewLine => write!(self.stdout, "{}", termion::clear::UntilNewline)?, + }; + self.stdout.flush() + } + + fn append_lines(&mut self, n: u16) -> io::Result<()> { + for _ in 0..n { + writeln!(self.stdout)?; + } + self.stdout.flush() + } + + /// Hides cursor + fn hide_cursor(&mut self) -> io::Result<()> { + write!(self.stdout, "{}", termion::cursor::Hide)?; + self.stdout.flush() + } + + /// Shows cursor + fn show_cursor(&mut self) -> io::Result<()> { + write!(self.stdout, "{}", termion::cursor::Show)?; + self.stdout.flush() + } + + /// Gets cursor position (0-based index) + fn get_cursor(&mut self) -> io::Result<(u16, u16)> { + termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1)) + } + + /// Sets cursor position (0-based index) + fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { + write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?; + self.stdout.flush() + } + + fn draw<'a, I>(&mut self, content: I) -> io::Result<()> + where + I: Iterator<Item = (u16, u16, &'a Cell)>, + { + use std::fmt::Write; + + let mut string = String::with_capacity(content.size_hint().0 * 3); + let mut fg = Color::Reset; + let mut bg = Color::Reset; + let mut modifier = Modifier::empty(); + let mut last_pos: Option<(u16, u16)> = None; + for (x, y, cell) in content { + // Move the cursor if the previous location was not (x - 1, y) + if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) { + write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap(); + } + last_pos = Some((x, y)); + if cell.modifier != modifier { + write!( + string, + "{}", + ModifierDiff { + from: modifier, + to: cell.modifier + } + ) + .unwrap(); + modifier = cell.modifier; + } + if cell.fg != fg { + write!(string, "{}", Fg(cell.fg)).unwrap(); + fg = cell.fg; + } + if cell.bg != bg { + write!(string, "{}", Bg(cell.bg)).unwrap(); + bg = cell.bg; + } + string.push_str(&cell.symbol); + } + write!( + self.stdout, + "{}{}{}{}", + string, + Fg(Color::Reset), + Bg(Color::Reset), + termion::style::Reset, + ) + } + + /// Return the size of the terminal + fn size(&self) -> io::Result<Rect> { + let terminal = termion::terminal_size()?; + Ok(Rect::new(0, 0, terminal.0, terminal.1)) + } + + fn flush(&mut self) -> io::Result<()> { + self.stdout.flush() + } +} + +struct Fg(Color); + +struct Bg(Color); + +struct ModifierDiff { + from: Modifier, + to: Modifier, +} + +impl fmt::Display for Fg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use termion::color::Color as TermionColor; + match self.0 { + Color::Reset => termion::color::Reset.write_fg(f), + Color::Black => termion::color::Black.write_fg(f), + Color::Red => termion::color::Red.write_fg(f), + Color::Green => termion::color::Green.write_fg(f), + Color::Yellow => termion::color::Yellow.write_fg(f), + Color::Blue => termion::color::Blue.write_fg(f), + Color::Magenta => termion::color::Magenta.write_fg(f), + Color::Cyan => termion::color::Cyan.write_fg(f), + Color::Gray => termion::color::White.write_fg(f), + Color::DarkGray => termion::color::LightBlack.write_fg(f), + Color::LightRed => termion::color::LightRed.write_fg(f), + Color::LightGreen => termion::color::LightGreen.write_fg(f), + Color::LightBlue => termion::color::LightBlue.write_fg(f), + Color::LightYellow => termion::color::LightYellow.write_fg(f), + Color::LightMagenta => termion::color::LightMagenta.write_fg(f), + Color::LightCyan => termion::color::LightCyan.write_fg(f), + Color::White => termion::color::LightWhite.write_fg(f), + Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f), + Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f), + } + } +} +impl fmt::Display for Bg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use termion::color::Color as TermionColor; + match self.0 { + Color::Reset => termion::color::Reset.write_bg(f), + Color::Black => termion::color::Black.write_bg(f), + Color::Red => termion::color::Red.write_bg(f), + Color::Green => termion::color::Green.write_bg(f), + Color::Yellow => termion::color::Yellow.write_bg(f), + Color::Blue => termion::color::Blue.write_bg(f), + Color::Magenta => termion::color::Magenta.write_bg(f), + Color::Cyan => termion::color::Cyan.write_bg(f), + Color::Gray => termion::color::White.write_bg(f), + Color::DarkGray => termion::color::LightBlack.write_bg(f), + Color::LightRed => termion::color::LightRed.write_bg(f), + Color::LightGreen => termion::color::LightGreen.write_bg(f), + Color::LightBlue => termion::color::LightBlue.write_bg(f), + Color::LightYellow => termion::color::LightYellow.write_bg(f), + Color::LightMagenta => termion::color::LightMagenta.write_bg(f), + Color::LightCyan => termion::color::LightCyan.write_bg(f), + Color::White => termion::color::LightWhite.write_bg(f), + Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f), + Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f), + } + } +} + +impl fmt::Display for ModifierDiff { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let remove = self.from - self.to; + if remove.contains(Modifier::REVERSED) { + write!(f, "{}", termion::style::NoInvert)?; + } + if remove.contains(Modifier::BOLD) { + // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant + // terminals, and NoFaint additionally disables bold... so we use this trick to get + // the right semantics. + write!(f, "{}", termion::style::NoFaint)?; + + if self.to.contains(Modifier::DIM) { + write!(f, "{}", termion::style::Faint)?; + } + } + if remove.contains(Modifier::ITALIC) { + write!(f, "{}", termion::style::NoItalic)?; + } + if remove.contains(Modifier::UNDERLINED) { + write!(f, "{}", termion::style::NoUnderline)?; + } + if remove.contains(Modifier::DIM) { + write!(f, "{}", termion::style::NoFaint)?; + + // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it + // here if we want it. + if self.to.contains(Modifier::BOLD) { + write!(f, "{}", termion::style::Bold)?; + } + } + if remove.contains(Modifier::CROSSED_OUT) { + write!(f, "{}", termion::style::NoCrossedOut)?; + } + if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) { + write!(f, "{}", termion::style::NoBlink)?; + } + + let add = self.to - self.from; + if add.contains(Modifier::REVERSED) { + write!(f, "{}", termion::style::Invert)?; + } + if add.contains(Modifier::BOLD) { + write!(f, "{}", termion::style::Bold)?; + } + if add.contains(Modifier::ITALIC) { + write!(f, "{}", termion::style::Italic)?; + } + if add.contains(Modifier::UNDERLINED) { + write!(f, "{}", termion::style::Underline)?; + } + if add.contains(Modifier::DIM) { + write!(f, "{}", termion::style::Faint)?; + } + if add.contains(Modifier::CROSSED_OUT) { + write!(f, "{}", termion::style::CrossedOut)?; + } + if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) { + write!(f, "{}", termion::style::Blink)?; + } + + Ok(()) + } +} |
