Skip to main content

ratatui_termion/
lib.rs

1// show the feature flags in the generated documentation
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
5    html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
6)]
7#![warn(missing_docs)]
8//! This module provides the [`TermionBackend`] implementation for the [`Backend`] trait. It uses
9//! the [Termion] crate to interact with the terminal.
10//!
11//! Most application authors should start with the main [`ratatui`] crate and only depend on
12//! `ratatui-termion` directly when they specifically want the Termion backend. This crate is the
13//! backend layer, not the primary docs.rs entry point for building applications.
14//!
15//! [`Backend`]: ratatui_core::backend::Backend
16//! [Termion]: https://docs.rs/termion
17//!
18//! # Crate Organization
19//!
20//! `ratatui-termion` is part of the Ratatui workspace that was modularized in version 0.30.0.
21//! This crate provides the [Termion] backend implementation for Ratatui.
22//!
23//! **When to use `ratatui-termion`:**
24//!
25//! - You want to depend on the Termion backend crate directly
26//! - You prefer Termion's Unix-focused approach
27//!
28//! **When to use the main [`ratatui`] crate:**
29//!
30//! - Building applications
31//! - You want backend selection to stay behind Ratatui's re-exports
32//!
33//! For detailed information about the workspace organization, see [ARCHITECTURE.md].
34//!
35//! [`ratatui`]: https://crates.io/crates/ratatui
36//! [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
37#![cfg_attr(feature = "document-features", doc = "\n## Features")]
38#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
39
40use std::fmt;
41use std::io::{self, Write};
42
43use ratatui_core::backend::{Backend, ClearType, WindowSize};
44use ratatui_core::buffer::Cell;
45use ratatui_core::layout::{Position, Size};
46use ratatui_core::style::{Color, Modifier, Style};
47pub use termion;
48use termion::color::Color as _;
49use termion::{color as tcolor, style as tstyle};
50
51/// A [`Backend`] implementation that uses [Termion] to render to the terminal.
52///
53/// The `TermionBackend` struct is a wrapper around a writer implementing [`Write`], which is used
54/// to send commands to the terminal. It provides methods for drawing content, manipulating the
55/// cursor, and clearing the terminal screen.
56///
57/// Most applications should not call the methods on `TermionBackend` directly, but will instead
58/// use the [`Terminal`] struct, which provides a more ergonomic interface.
59///
60/// Usually applications will enable raw mode and switch to alternate screen mode when starting.
61/// This is done by calling [`IntoRawMode::into_raw_mode()`] and
62/// [`IntoAlternateScreen::into_alternate_screen()`] on the writer before creating the backend.
63/// This is not done automatically by the backend because it is possible that the application may
64/// want to use the terminal for other purposes (like showing help text) before entering alternate
65/// screen mode. This backend automatically disable raw mode and switches back to the primary
66/// screen when the writer is dropped.
67///
68/// # Example
69///
70/// ```rust,ignore
71/// use std::io::{stderr, stdout};
72///
73/// use ratatui::Terminal;
74/// use ratatui::backend::TermionBackend;
75/// use ratatui::termion::raw::IntoRawMode;
76/// use ratatui::termion::screen::IntoAlternateScreen;
77///
78/// let writer = stdout().into_raw_mode()?.into_alternate_screen()?;
79/// let mut backend = TermionBackend::new(writer);
80/// // or
81/// let writer = stderr().into_raw_mode()?.into_alternate_screen()?;
82/// let backend = TermionBackend::new(stderr());
83/// let mut terminal = Terminal::new(backend)?;
84///
85/// terminal.clear()?;
86/// terminal.draw(|frame| {
87///     // -- snip --
88/// })?;
89/// # std::io::Result::Ok(())
90/// ```
91///
92/// [`IntoRawMode::into_raw_mode()`]: termion::raw::IntoRawMode
93/// [`IntoAlternateScreen::into_alternate_screen()`]: termion::screen::IntoAlternateScreen
94/// [`Terminal`]: ratatui_core::terminal::Terminal
95/// [Termion]: https://docs.rs/termion
96#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
97pub struct TermionBackend<W>
98where
99    W: Write,
100{
101    writer: W,
102}
103
104impl<W> TermionBackend<W>
105where
106    W: Write,
107{
108    /// Creates a new Termion backend with the given writer.
109    ///
110    /// Most applications will use either [`stdout`](std::io::stdout) or
111    /// [`stderr`](std::io::stderr) as writer. See the [FAQ] to determine which one to use.
112    ///
113    /// [FAQ]: https://ratatui.rs/faq/#should-i-use-stdout-or-stderr
114    ///
115    /// # Example
116    ///
117    /// ```rust,ignore
118    /// use std::io::stdout;
119    ///
120    /// use ratatui::backend::TermionBackend;
121    ///
122    /// let backend = TermionBackend::new(stdout());
123    /// ```
124    pub const fn new(writer: W) -> Self {
125        Self { writer }
126    }
127
128    /// Gets the writer.
129    #[instability::unstable(
130        feature = "backend-writer",
131        issue = "https://github.com/ratatui/ratatui/pull/991"
132    )]
133    pub const fn writer(&self) -> &W {
134        &self.writer
135    }
136
137    /// Gets the writer as a mutable reference.
138    /// Note: writing to the writer may cause incorrect output after the write. This is due to the
139    /// way that the Terminal implements diffing Buffers.
140    #[instability::unstable(
141        feature = "backend-writer",
142        issue = "https://github.com/ratatui/ratatui/pull/991"
143    )]
144    pub const fn writer_mut(&mut self) -> &mut W {
145        &mut self.writer
146    }
147}
148
149impl<W> Write for TermionBackend<W>
150where
151    W: Write,
152{
153    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
154        self.writer.write(buf)
155    }
156
157    fn flush(&mut self) -> io::Result<()> {
158        self.writer.flush()
159    }
160}
161
162impl<W> Backend for TermionBackend<W>
163where
164    W: Write,
165{
166    type Error = io::Error;
167
168    fn clear(&mut self) -> io::Result<()> {
169        self.clear_region(ClearType::All)
170    }
171
172    fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
173        match clear_type {
174            ClearType::All => write!(self.writer, "{}", termion::clear::All)?,
175            ClearType::AfterCursor => write!(self.writer, "{}", termion::clear::AfterCursor)?,
176            ClearType::BeforeCursor => write!(self.writer, "{}", termion::clear::BeforeCursor)?,
177            ClearType::CurrentLine => write!(self.writer, "{}", termion::clear::CurrentLine)?,
178            ClearType::UntilNewLine => write!(self.writer, "{}", termion::clear::UntilNewline)?,
179        }
180        self.writer.flush()
181    }
182
183    fn append_lines(&mut self, n: u16) -> io::Result<()> {
184        for _ in 0..n {
185            writeln!(self.writer)?;
186        }
187        self.writer.flush()
188    }
189
190    fn hide_cursor(&mut self) -> io::Result<()> {
191        write!(self.writer, "{}", termion::cursor::Hide)?;
192        self.writer.flush()
193    }
194
195    fn show_cursor(&mut self) -> io::Result<()> {
196        write!(self.writer, "{}", termion::cursor::Show)?;
197        self.writer.flush()
198    }
199
200    fn get_cursor_position(&mut self) -> io::Result<Position> {
201        termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer)
202            .map(|(x, y)| Position { x: x - 1, y: y - 1 })
203    }
204
205    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
206        let Position { x, y } = position.into();
207        write!(self.writer, "{}", termion::cursor::Goto(x + 1, y + 1))?;
208        self.writer.flush()
209    }
210
211    fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
212    where
213        I: Iterator<Item = (u16, u16, &'a Cell)>,
214    {
215        use std::fmt::Write;
216
217        let mut string = String::with_capacity(content.size_hint().0 * 3);
218        let mut fg = Color::Reset;
219        let mut bg = Color::Reset;
220        let mut modifier = Modifier::empty();
221        let mut last_pos: Option<Position> = None;
222        for (x, y, cell) in content {
223            // Move the cursor if the previous location was not (x - 1, y)
224            if !matches!(last_pos, Some(p) if x == p.x + 1 && y == p.y) {
225                write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
226            }
227            last_pos = Some(Position { x, y });
228            if cell.modifier != modifier {
229                write!(
230                    string,
231                    "{}",
232                    ModifierDiff {
233                        from: modifier,
234                        to: cell.modifier
235                    }
236                )
237                .unwrap();
238                modifier = cell.modifier;
239            }
240            if cell.fg != fg {
241                write!(string, "{}", Fg(cell.fg)).unwrap();
242                fg = cell.fg;
243            }
244            if cell.bg != bg {
245                write!(string, "{}", Bg(cell.bg)).unwrap();
246                bg = cell.bg;
247            }
248            string.push_str(cell.symbol());
249        }
250        write!(
251            self.writer,
252            "{string}{}{}{}",
253            Fg(Color::Reset),
254            Bg(Color::Reset),
255            termion::style::Reset,
256        )
257    }
258
259    fn size(&self) -> io::Result<Size> {
260        let terminal = termion::terminal_size()?;
261        Ok(Size::new(terminal.0, terminal.1))
262    }
263
264    fn window_size(&mut self) -> io::Result<WindowSize> {
265        Ok(WindowSize {
266            columns_rows: termion::terminal_size()?.into(),
267            pixels: termion::terminal_size_pixels()?.into(),
268        })
269    }
270
271    fn flush(&mut self) -> io::Result<()> {
272        self.writer.flush()
273    }
274
275    #[cfg(feature = "scrolling-regions")]
276    fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
277        write!(
278            self.writer,
279            "{}{}{}",
280            SetRegion(region.start.saturating_add(1), region.end),
281            termion::scroll::Up(amount),
282            ResetRegion,
283        )?;
284        self.writer.flush()
285    }
286
287    #[cfg(feature = "scrolling-regions")]
288    fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
289        write!(
290            self.writer,
291            "{}{}{}",
292            SetRegion(region.start.saturating_add(1), region.end),
293            termion::scroll::Down(amount),
294            ResetRegion,
295        )?;
296        self.writer.flush()
297    }
298}
299struct Fg(Color);
300
301struct Bg(Color);
302
303/// The `ModifierDiff` struct is used to calculate the difference between two `Modifier`
304/// values. This is useful when updating the terminal display, as it allows for more
305/// efficient updates by only sending the necessary changes.
306struct ModifierDiff {
307    from: Modifier,
308    to: Modifier,
309}
310
311impl fmt::Display for Fg {
312    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313        match self.0 {
314            Color::Reset => termion::color::Reset.write_fg(f),
315            Color::Black => termion::color::Black.write_fg(f),
316            Color::Red => termion::color::Red.write_fg(f),
317            Color::Green => termion::color::Green.write_fg(f),
318            Color::Yellow => termion::color::Yellow.write_fg(f),
319            Color::Blue => termion::color::Blue.write_fg(f),
320            Color::Magenta => termion::color::Magenta.write_fg(f),
321            Color::Cyan => termion::color::Cyan.write_fg(f),
322            Color::Gray => termion::color::White.write_fg(f),
323            Color::DarkGray => termion::color::LightBlack.write_fg(f),
324            Color::LightRed => termion::color::LightRed.write_fg(f),
325            Color::LightGreen => termion::color::LightGreen.write_fg(f),
326            Color::LightBlue => termion::color::LightBlue.write_fg(f),
327            Color::LightYellow => termion::color::LightYellow.write_fg(f),
328            Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
329            Color::LightCyan => termion::color::LightCyan.write_fg(f),
330            Color::White => termion::color::LightWhite.write_fg(f),
331            Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
332            Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
333        }
334    }
335}
336impl fmt::Display for Bg {
337    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
338        match self.0 {
339            Color::Reset => termion::color::Reset.write_bg(f),
340            Color::Black => termion::color::Black.write_bg(f),
341            Color::Red => termion::color::Red.write_bg(f),
342            Color::Green => termion::color::Green.write_bg(f),
343            Color::Yellow => termion::color::Yellow.write_bg(f),
344            Color::Blue => termion::color::Blue.write_bg(f),
345            Color::Magenta => termion::color::Magenta.write_bg(f),
346            Color::Cyan => termion::color::Cyan.write_bg(f),
347            Color::Gray => termion::color::White.write_bg(f),
348            Color::DarkGray => termion::color::LightBlack.write_bg(f),
349            Color::LightRed => termion::color::LightRed.write_bg(f),
350            Color::LightGreen => termion::color::LightGreen.write_bg(f),
351            Color::LightBlue => termion::color::LightBlue.write_bg(f),
352            Color::LightYellow => termion::color::LightYellow.write_bg(f),
353            Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
354            Color::LightCyan => termion::color::LightCyan.write_bg(f),
355            Color::White => termion::color::LightWhite.write_bg(f),
356            Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
357            Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
358        }
359    }
360}
361
362/// A trait for converting a Termion type to a Ratatui type.
363///
364/// This trait is necessary to avoid the orphan rule, as we cannot implement a trait for a type
365/// defined in another crate.
366pub trait FromTermion<T> {
367    /// Convert the Termion type to the Ratatui type.
368    fn from_termion(termion: T) -> Self;
369}
370
371/// A trait for converting a Ratatui type to a Termion type.
372///
373/// This trait is necessary to avoid the orphan rule, as we cannot implement a trait for a type
374/// defined in another crate.
375pub trait IntoTermion<T> {
376    /// Convert the Ratatui type to the Termion type.
377    fn into_termion(self) -> T;
378}
379
380macro_rules! from_termion_for_color {
381    ($termion_color:ident, $color:ident) => {
382        impl FromTermion<tcolor::$termion_color> for Color {
383            fn from_termion(_: tcolor::$termion_color) -> Self {
384                Color::$color
385            }
386        }
387
388        impl FromTermion<tcolor::Bg<tcolor::$termion_color>> for Style {
389            fn from_termion(_: tcolor::Bg<tcolor::$termion_color>) -> Self {
390                Style::default().bg(Color::$color)
391            }
392        }
393
394        impl FromTermion<tcolor::Fg<tcolor::$termion_color>> for Style {
395            fn from_termion(_: tcolor::Fg<tcolor::$termion_color>) -> Self {
396                Style::default().fg(Color::$color)
397            }
398        }
399    };
400}
401
402from_termion_for_color!(Reset, Reset);
403from_termion_for_color!(Black, Black);
404from_termion_for_color!(Red, Red);
405from_termion_for_color!(Green, Green);
406from_termion_for_color!(Yellow, Yellow);
407from_termion_for_color!(Blue, Blue);
408from_termion_for_color!(Magenta, Magenta);
409from_termion_for_color!(Cyan, Cyan);
410from_termion_for_color!(White, Gray);
411from_termion_for_color!(LightBlack, DarkGray);
412from_termion_for_color!(LightRed, LightRed);
413from_termion_for_color!(LightGreen, LightGreen);
414from_termion_for_color!(LightBlue, LightBlue);
415from_termion_for_color!(LightYellow, LightYellow);
416from_termion_for_color!(LightMagenta, LightMagenta);
417from_termion_for_color!(LightCyan, LightCyan);
418from_termion_for_color!(LightWhite, White);
419
420impl FromTermion<tcolor::AnsiValue> for Color {
421    fn from_termion(value: tcolor::AnsiValue) -> Self {
422        Self::Indexed(value.0)
423    }
424}
425
426impl FromTermion<tcolor::Bg<tcolor::AnsiValue>> for Style {
427    fn from_termion(value: tcolor::Bg<tcolor::AnsiValue>) -> Self {
428        Self::default().bg(Color::Indexed(value.0.0))
429    }
430}
431
432impl FromTermion<tcolor::Fg<tcolor::AnsiValue>> for Style {
433    fn from_termion(value: tcolor::Fg<tcolor::AnsiValue>) -> Self {
434        Self::default().fg(Color::Indexed(value.0.0))
435    }
436}
437
438impl FromTermion<tcolor::Rgb> for Color {
439    fn from_termion(value: tcolor::Rgb) -> Self {
440        Self::Rgb(value.0, value.1, value.2)
441    }
442}
443
444impl FromTermion<tcolor::Bg<tcolor::Rgb>> for Style {
445    fn from_termion(value: tcolor::Bg<tcolor::Rgb>) -> Self {
446        Self::default().bg(Color::Rgb(value.0.0, value.0.1, value.0.2))
447    }
448}
449
450impl FromTermion<tcolor::Fg<tcolor::Rgb>> for Style {
451    fn from_termion(value: tcolor::Fg<tcolor::Rgb>) -> Self {
452        Self::default().fg(Color::Rgb(value.0.0, value.0.1, value.0.2))
453    }
454}
455
456impl fmt::Display for ModifierDiff {
457    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458        let remove = self.from - self.to;
459        if remove.contains(Modifier::REVERSED) {
460            write!(f, "{}", termion::style::NoInvert)?;
461        }
462        if remove.contains(Modifier::BOLD) {
463            // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
464            // terminals, and NoFaint additionally disables bold... so we use this trick to get
465            // the right semantics.
466            write!(f, "{}", termion::style::NoFaint)?;
467
468            if self.to.contains(Modifier::DIM) {
469                write!(f, "{}", termion::style::Faint)?;
470            }
471        }
472        if remove.contains(Modifier::ITALIC) {
473            write!(f, "{}", termion::style::NoItalic)?;
474        }
475        if remove.contains(Modifier::UNDERLINED) {
476            write!(f, "{}", termion::style::NoUnderline)?;
477        }
478        if remove.contains(Modifier::DIM) {
479            write!(f, "{}", termion::style::NoFaint)?;
480
481            // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
482            // here if we want it.
483            if self.to.contains(Modifier::BOLD) {
484                write!(f, "{}", termion::style::Bold)?;
485            }
486        }
487        if remove.contains(Modifier::CROSSED_OUT) {
488            write!(f, "{}", termion::style::NoCrossedOut)?;
489        }
490        if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
491            write!(f, "{}", termion::style::NoBlink)?;
492        }
493
494        let add = self.to - self.from;
495        if add.contains(Modifier::REVERSED) {
496            write!(f, "{}", termion::style::Invert)?;
497        }
498        if add.contains(Modifier::BOLD) {
499            write!(f, "{}", termion::style::Bold)?;
500        }
501        if add.contains(Modifier::ITALIC) {
502            write!(f, "{}", termion::style::Italic)?;
503        }
504        if add.contains(Modifier::UNDERLINED) {
505            write!(f, "{}", termion::style::Underline)?;
506        }
507        if add.contains(Modifier::DIM) {
508            write!(f, "{}", termion::style::Faint)?;
509        }
510        if add.contains(Modifier::CROSSED_OUT) {
511            write!(f, "{}", termion::style::CrossedOut)?;
512        }
513        if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
514            write!(f, "{}", termion::style::Blink)?;
515        }
516
517        Ok(())
518    }
519}
520
521macro_rules! from_termion_for_modifier {
522    ($termion_modifier:ident, $modifier:ident) => {
523        impl FromTermion<tstyle::$termion_modifier> for Modifier {
524            fn from_termion(_: tstyle::$termion_modifier) -> Self {
525                Modifier::$modifier
526            }
527        }
528    };
529}
530
531from_termion_for_modifier!(Invert, REVERSED);
532from_termion_for_modifier!(Bold, BOLD);
533from_termion_for_modifier!(Italic, ITALIC);
534from_termion_for_modifier!(Underline, UNDERLINED);
535from_termion_for_modifier!(Faint, DIM);
536from_termion_for_modifier!(CrossedOut, CROSSED_OUT);
537from_termion_for_modifier!(Blink, SLOW_BLINK);
538
539impl FromTermion<termion::style::Reset> for Modifier {
540    fn from_termion(_: termion::style::Reset) -> Self {
541        Self::empty()
542    }
543}
544
545/// Set scrolling region.
546#[derive(Copy, Clone, PartialEq, Eq)]
547pub struct SetRegion(pub u16, pub u16);
548
549impl fmt::Display for SetRegion {
550    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
551        write!(f, "\x1B[{};{}r", self.0, self.1)
552    }
553}
554
555/// Reset scrolling region.
556#[derive(Copy, Clone, PartialEq, Eq)]
557pub struct ResetRegion;
558
559impl fmt::Display for ResetRegion {
560    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561        write!(f, "\x1B[r")
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568
569    #[test]
570    fn from_termion_color() {
571        assert_eq!(Color::from_termion(tcolor::Reset), Color::Reset);
572        assert_eq!(Color::from_termion(tcolor::Black), Color::Black);
573        assert_eq!(Color::from_termion(tcolor::Red), Color::Red);
574        assert_eq!(Color::from_termion(tcolor::Green), Color::Green);
575        assert_eq!(Color::from_termion(tcolor::Yellow), Color::Yellow);
576        assert_eq!(Color::from_termion(tcolor::Blue), Color::Blue);
577        assert_eq!(Color::from_termion(tcolor::Magenta), Color::Magenta);
578        assert_eq!(Color::from_termion(tcolor::Cyan), Color::Cyan);
579        assert_eq!(Color::from_termion(tcolor::White), Color::Gray);
580        assert_eq!(Color::from_termion(tcolor::LightBlack), Color::DarkGray);
581        assert_eq!(Color::from_termion(tcolor::LightRed), Color::LightRed);
582        assert_eq!(Color::from_termion(tcolor::LightGreen), Color::LightGreen);
583        assert_eq!(Color::from_termion(tcolor::LightBlue), Color::LightBlue);
584        assert_eq!(Color::from_termion(tcolor::LightYellow), Color::LightYellow);
585        assert_eq!(
586            Color::from_termion(tcolor::LightMagenta),
587            Color::LightMagenta
588        );
589        assert_eq!(Color::from_termion(tcolor::LightCyan), Color::LightCyan);
590        assert_eq!(Color::from_termion(tcolor::LightWhite), Color::White);
591        assert_eq!(
592            Color::from_termion(tcolor::AnsiValue(31)),
593            Color::Indexed(31)
594        );
595        assert_eq!(
596            Color::from_termion(tcolor::Rgb(1, 2, 3)),
597            Color::Rgb(1, 2, 3)
598        );
599    }
600
601    #[test]
602    fn from_termion_bg() {
603        use tc::Bg;
604        use tcolor as tc;
605
606        assert_eq!(
607            Style::from_termion(Bg(tc::Reset)),
608            Style::new().bg(Color::Reset)
609        );
610        assert_eq!(Style::from_termion(Bg(tc::Black)), Style::new().on_black());
611        assert_eq!(Style::from_termion(Bg(tc::Red)), Style::new().on_red());
612        assert_eq!(Style::from_termion(Bg(tc::Green)), Style::new().on_green());
613        assert_eq!(
614            Style::from_termion(Bg(tc::Yellow)),
615            Style::new().on_yellow()
616        );
617        assert_eq!(Style::from_termion(Bg(tc::Blue)), Style::new().on_blue());
618        assert_eq!(
619            Style::from_termion(Bg(tc::Magenta)),
620            Style::new().on_magenta()
621        );
622        assert_eq!(Style::from_termion(Bg(tc::Cyan)), Style::new().on_cyan());
623        assert_eq!(Style::from_termion(Bg(tc::White)), Style::new().on_gray());
624        assert_eq!(
625            Style::from_termion(Bg(tc::LightBlack)),
626            Style::new().on_dark_gray()
627        );
628        assert_eq!(
629            Style::from_termion(Bg(tc::LightRed)),
630            Style::new().on_light_red()
631        );
632        assert_eq!(
633            Style::from_termion(Bg(tc::LightGreen)),
634            Style::new().on_light_green()
635        );
636        assert_eq!(
637            Style::from_termion(Bg(tc::LightBlue)),
638            Style::new().on_light_blue()
639        );
640        assert_eq!(
641            Style::from_termion(Bg(tc::LightYellow)),
642            Style::new().on_light_yellow()
643        );
644        assert_eq!(
645            Style::from_termion(Bg(tc::LightMagenta)),
646            Style::new().on_light_magenta()
647        );
648        assert_eq!(
649            Style::from_termion(Bg(tc::LightCyan)),
650            Style::new().on_light_cyan()
651        );
652        assert_eq!(
653            Style::from_termion(Bg(tc::LightWhite)),
654            Style::new().on_white()
655        );
656        assert_eq!(
657            Style::from_termion(Bg(tc::AnsiValue(31))),
658            Style::new().bg(Color::Indexed(31))
659        );
660        assert_eq!(
661            Style::from_termion(Bg(tc::Rgb(1, 2, 3))),
662            Style::new().bg(Color::Rgb(1, 2, 3))
663        );
664    }
665
666    #[test]
667    fn from_termion_fg() {
668        use tc::Fg;
669        use tcolor as tc;
670
671        assert_eq!(
672            Style::from_termion(Fg(tc::Reset)),
673            Style::new().fg(Color::Reset)
674        );
675        assert_eq!(Style::from_termion(Fg(tc::Black)), Style::new().black());
676        assert_eq!(Style::from_termion(Fg(tc::Red)), Style::new().red());
677        assert_eq!(Style::from_termion(Fg(tc::Green)), Style::new().green());
678        assert_eq!(Style::from_termion(Fg(tc::Yellow)), Style::new().yellow());
679        assert_eq!(Style::from_termion(Fg(tc::Blue)), Style::default().blue());
680        assert_eq!(
681            Style::from_termion(Fg(tc::Magenta)),
682            Style::default().magenta()
683        );
684        assert_eq!(Style::from_termion(Fg(tc::Cyan)), Style::default().cyan());
685        assert_eq!(Style::from_termion(Fg(tc::White)), Style::default().gray());
686        assert_eq!(
687            Style::from_termion(Fg(tc::LightBlack)),
688            Style::new().dark_gray()
689        );
690        assert_eq!(
691            Style::from_termion(Fg(tc::LightRed)),
692            Style::new().light_red()
693        );
694        assert_eq!(
695            Style::from_termion(Fg(tc::LightGreen)),
696            Style::new().light_green()
697        );
698        assert_eq!(
699            Style::from_termion(Fg(tc::LightBlue)),
700            Style::new().light_blue()
701        );
702        assert_eq!(
703            Style::from_termion(Fg(tc::LightYellow)),
704            Style::new().light_yellow()
705        );
706        assert_eq!(
707            Style::from_termion(Fg(tc::LightMagenta)),
708            Style::new().light_magenta()
709        );
710        assert_eq!(
711            Style::from_termion(Fg(tc::LightCyan)),
712            Style::new().light_cyan()
713        );
714        assert_eq!(
715            Style::from_termion(Fg(tc::LightWhite)),
716            Style::new().white()
717        );
718        assert_eq!(
719            Style::from_termion(Fg(tc::AnsiValue(31))),
720            Style::default().fg(Color::Indexed(31))
721        );
722        assert_eq!(
723            Style::from_termion(Fg(tc::Rgb(1, 2, 3))),
724            Style::default().fg(Color::Rgb(1, 2, 3))
725        );
726    }
727
728    #[test]
729    fn from_termion_style() {
730        assert_eq!(Modifier::from_termion(tstyle::Invert), Modifier::REVERSED);
731        assert_eq!(Modifier::from_termion(tstyle::Bold), Modifier::BOLD);
732        assert_eq!(Modifier::from_termion(tstyle::Italic), Modifier::ITALIC);
733        assert_eq!(
734            Modifier::from_termion(tstyle::Underline),
735            Modifier::UNDERLINED
736        );
737        assert_eq!(Modifier::from_termion(tstyle::Faint), Modifier::DIM);
738        assert_eq!(
739            Modifier::from_termion(tstyle::CrossedOut),
740            Modifier::CROSSED_OUT
741        );
742        assert_eq!(Modifier::from_termion(tstyle::Blink), Modifier::SLOW_BLINK);
743        assert_eq!(Modifier::from_termion(tstyle::Reset), Modifier::empty());
744    }
745}