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