Skip to main content

ratatui_termwiz/
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 [`TermwizBackend`] implementation for the [`Backend`] trait. It uses
9//! the [Termwiz] crate to interact with the terminal.
10//!
11//! Most application authors should start with the main [`ratatui`] crate and only depend on
12//! `ratatui-termwiz` directly when they specifically want the Termwiz backend or its advanced
13//! terminal capabilities. This crate is the backend layer, not the primary docs.rs entry point for
14//! building applications.
15//!
16//! [`Backend`]: trait.Backend.html
17//! [Termwiz]: https://crates.io/crates/termwiz
18//!
19//! # Crate Organization
20//!
21//! `ratatui-termwiz` is part of the Ratatui workspace that was modularized in version 0.30.0.
22//! This crate provides the [Termwiz] backend implementation for Ratatui.
23//!
24//! **When to use `ratatui-termwiz`:**
25//!
26//! - You want to depend on the Termwiz backend crate directly
27//! - You need Termwiz's advanced terminal capabilities
28//!
29//! **When to use the main [`ratatui`] crate:**
30//!
31//! - Building applications
32//! - You want backend selection to stay behind Ratatui's re-exports
33//!
34//! For detailed information about the workspace organization, see [ARCHITECTURE.md].
35//!
36//! [`ratatui`]: https://crates.io/crates/ratatui
37//! [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
38#![cfg_attr(feature = "document-features", doc = "\n## Features")]
39#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
40
41use std::error::Error;
42use std::io;
43
44use ratatui_core::backend::{Backend, ClearType, WindowSize};
45use ratatui_core::buffer::Cell;
46use ratatui_core::layout::{Position, Size};
47use ratatui_core::style::{Color, Modifier, Style};
48pub use termwiz;
49use termwiz::caps::Capabilities;
50use termwiz::cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline};
51use termwiz::color::{AnsiColor, ColorAttribute, ColorSpec, LinearRgba, RgbColor, SrgbaTuple};
52use termwiz::surface::{Change, CursorVisibility, Position as TermwizPosition};
53use termwiz::terminal::buffered::BufferedTerminal;
54use termwiz::terminal::{ScreenSize, SystemTerminal, Terminal};
55
56/// A [`Backend`] implementation that uses [Termwiz] to render to the terminal.
57///
58/// The `TermwizBackend` struct is a wrapper around a [`BufferedTerminal`], which is used to send
59/// commands to the terminal. It provides methods for drawing content, manipulating the cursor, and
60/// clearing the terminal screen.
61///
62/// Most applications should not call the methods on `TermwizBackend` directly, but will instead
63/// use the [`Terminal`] struct, which provides a more ergonomic interface.
64///
65/// This backend automatically enables raw mode and switches to the alternate screen when it is
66/// created using the [`TermwizBackend::new`] method (and disables raw mode and returns to the main
67/// screen when dropped). Use the [`TermwizBackend::with_buffered_terminal`] to create a new
68/// instance with a custom [`BufferedTerminal`] if this is not desired.
69///
70/// # Example
71///
72/// ```rust,no_run
73/// use ratatui::Terminal;
74/// use ratatui::backend::TermwizBackend;
75///
76/// let backend = TermwizBackend::new()?;
77/// let mut terminal = Terminal::new(backend)?;
78///
79/// terminal.clear()?;
80/// terminal.draw(|frame| {
81///     // -- snip --
82/// })?;
83/// # std::result::Result::Ok::<(), Box<dyn std::error::Error>>(())
84/// ```
85///
86/// See the [Examples] directory for more examples. See the [`backend`] module documentation
87/// for more details on raw mode and alternate screen.
88///
89/// [`backend`]: ratatui_core::backend
90/// [`Terminal`]: https://docs.rs/ratatui/latest/ratatui/struct.Terminal.html
91/// [`BufferedTerminal`]: termwiz::terminal::buffered::BufferedTerminal
92/// [Termwiz]: https://crates.io/crates/termwiz
93/// [Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui/examples/README.md
94pub struct TermwizBackend {
95    buffered_terminal: BufferedTerminal<SystemTerminal>,
96}
97
98impl TermwizBackend {
99    /// Creates a new Termwiz backend instance.
100    ///
101    /// The backend will automatically enable raw mode and enter the alternate screen.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error if unable to do any of the following:
106    /// - query the terminal capabilities.
107    /// - enter raw mode.
108    /// - enter the alternate screen.
109    /// - create the system or buffered terminal.
110    ///
111    /// # Example
112    ///
113    /// ```rust,no_run
114    /// use ratatui::backend::TermwizBackend;
115    ///
116    /// let backend = TermwizBackend::new()?;
117    /// # Ok::<(), Box<dyn std::error::Error>>(())
118    /// ```
119    pub fn new() -> Result<Self, Box<dyn Error>> {
120        let mut buffered_terminal =
121            BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?;
122        buffered_terminal.terminal().set_raw_mode()?;
123        buffered_terminal.terminal().enter_alternate_screen()?;
124        Ok(Self { buffered_terminal })
125    }
126
127    /// Creates a new Termwiz backend instance with the given buffered terminal.
128    pub const fn with_buffered_terminal(instance: BufferedTerminal<SystemTerminal>) -> Self {
129        Self {
130            buffered_terminal: instance,
131        }
132    }
133
134    /// Returns a reference to the buffered terminal used by the backend.
135    pub const fn buffered_terminal(&self) -> &BufferedTerminal<SystemTerminal> {
136        &self.buffered_terminal
137    }
138
139    /// Returns a mutable reference to the buffered terminal used by the backend.
140    pub const fn buffered_terminal_mut(&mut self) -> &mut BufferedTerminal<SystemTerminal> {
141        &mut self.buffered_terminal
142    }
143}
144
145impl Backend for TermwizBackend {
146    type Error = io::Error;
147
148    fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
149    where
150        I: Iterator<Item = (u16, u16, &'a Cell)>,
151    {
152        for (x, y, cell) in content {
153            self.buffered_terminal.add_changes(vec![
154                Change::CursorPosition {
155                    x: TermwizPosition::Absolute(x as usize),
156                    y: TermwizPosition::Absolute(y as usize),
157                },
158                Change::Attribute(AttributeChange::Foreground(cell.fg.into_termwiz())),
159                Change::Attribute(AttributeChange::Background(cell.bg.into_termwiz())),
160            ]);
161
162            self.buffered_terminal
163                .add_change(Change::Attribute(AttributeChange::Intensity(
164                    if cell.modifier.contains(Modifier::BOLD) {
165                        Intensity::Bold
166                    } else if cell.modifier.contains(Modifier::DIM) {
167                        Intensity::Half
168                    } else {
169                        Intensity::Normal
170                    },
171                )));
172
173            self.buffered_terminal
174                .add_change(Change::Attribute(AttributeChange::Italic(
175                    cell.modifier.contains(Modifier::ITALIC),
176                )));
177
178            self.buffered_terminal
179                .add_change(Change::Attribute(AttributeChange::Underline(
180                    if cell.modifier.contains(Modifier::UNDERLINED) {
181                        Underline::Single
182                    } else {
183                        Underline::None
184                    },
185                )));
186
187            self.buffered_terminal
188                .add_change(Change::Attribute(AttributeChange::Reverse(
189                    cell.modifier.contains(Modifier::REVERSED),
190                )));
191
192            self.buffered_terminal
193                .add_change(Change::Attribute(AttributeChange::Invisible(
194                    cell.modifier.contains(Modifier::HIDDEN),
195                )));
196
197            self.buffered_terminal
198                .add_change(Change::Attribute(AttributeChange::StrikeThrough(
199                    cell.modifier.contains(Modifier::CROSSED_OUT),
200                )));
201
202            self.buffered_terminal
203                .add_change(Change::Attribute(AttributeChange::Blink(
204                    if cell.modifier.contains(Modifier::SLOW_BLINK) {
205                        Blink::Slow
206                    } else if cell.modifier.contains(Modifier::RAPID_BLINK) {
207                        Blink::Rapid
208                    } else {
209                        Blink::None
210                    },
211                )));
212
213            self.buffered_terminal.add_change(cell.symbol());
214        }
215        Ok(())
216    }
217
218    fn hide_cursor(&mut self) -> io::Result<()> {
219        self.buffered_terminal
220            .add_change(Change::CursorVisibility(CursorVisibility::Hidden));
221        Ok(())
222    }
223
224    fn show_cursor(&mut self) -> io::Result<()> {
225        self.buffered_terminal
226            .add_change(Change::CursorVisibility(CursorVisibility::Visible));
227        Ok(())
228    }
229
230    fn get_cursor_position(&mut self) -> io::Result<Position> {
231        let (x, y) = self.buffered_terminal.cursor_position();
232        Ok(Position::new(x as u16, y as u16))
233    }
234
235    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
236        let Position { x, y } = position.into();
237        self.buffered_terminal.add_change(Change::CursorPosition {
238            x: TermwizPosition::Absolute(x as usize),
239            y: TermwizPosition::Absolute(y as usize),
240        });
241
242        Ok(())
243    }
244
245    fn clear(&mut self) -> io::Result<()> {
246        self.buffered_terminal
247            .add_change(Change::ClearScreen(termwiz::color::ColorAttribute::Default));
248        Ok(())
249    }
250
251    fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
252        match clear_type {
253            ClearType::All => self.clear(),
254            ClearType::AfterCursor
255            | ClearType::BeforeCursor
256            | ClearType::CurrentLine
257            | ClearType::UntilNewLine => Err(io::Error::other(format!(
258                "clear_type [{clear_type:?}] not supported with this backend"
259            ))),
260        }
261    }
262
263    fn size(&self) -> io::Result<Size> {
264        let (cols, rows) = self.buffered_terminal.dimensions();
265        Ok(Size::new(u16_max(cols), u16_max(rows)))
266    }
267
268    fn window_size(&mut self) -> io::Result<WindowSize> {
269        let ScreenSize {
270            cols,
271            rows,
272            xpixel,
273            ypixel,
274        } = self
275            .buffered_terminal
276            .terminal()
277            .get_screen_size()
278            .map_err(io::Error::other)?;
279        Ok(WindowSize {
280            columns_rows: Size {
281                width: u16_max(cols),
282                height: u16_max(rows),
283            },
284            pixels: Size {
285                width: u16_max(xpixel),
286                height: u16_max(ypixel),
287            },
288        })
289    }
290
291    fn flush(&mut self) -> io::Result<()> {
292        self.buffered_terminal.flush().map_err(io::Error::other)?;
293        Ok(())
294    }
295
296    #[cfg(feature = "scrolling-regions")]
297    fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
298        // termwiz doesn't have a command to just set the scrolling region. Instead, setting the
299        // scrolling region and scrolling are combined. However, this has the side-effect of
300        // leaving the scrolling region set. To reset the scrolling region, termwiz advises one to
301        // make a scrolling-region scroll command that contains the entire screen, but scrolls by 0
302        // lines. See [`Change::ScrollRegionUp`] for more details.
303        let (_, rows) = self.buffered_terminal.dimensions();
304        self.buffered_terminal.add_changes(vec![
305            Change::ScrollRegionUp {
306                first_row: region.start as usize,
307                region_size: region.len(),
308                scroll_count: amount as usize,
309            },
310            Change::ScrollRegionUp {
311                first_row: 0,
312                region_size: rows,
313                scroll_count: 0,
314            },
315        ]);
316        Ok(())
317    }
318
319    #[cfg(feature = "scrolling-regions")]
320    fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
321        // termwiz doesn't have a command to just set the scrolling region. Instead, setting the
322        // scrolling region and scrolling are combined. However, this has the side-effect of
323        // leaving the scrolling region set. To reset the scrolling region, termwiz advises one to
324        // make a scrolling-region scroll command that contains the entire screen, but scrolls by 0
325        // lines. See [`Change::ScrollRegionDown`] for more details.
326        let (_, rows) = self.buffered_terminal.dimensions();
327        self.buffered_terminal.add_changes(vec![
328            Change::ScrollRegionDown {
329                first_row: region.start as usize,
330                region_size: region.len(),
331                scroll_count: amount as usize,
332            },
333            Change::ScrollRegionDown {
334                first_row: 0,
335                region_size: rows,
336                scroll_count: 0,
337            },
338        ]);
339        Ok(())
340    }
341}
342
343/// A trait for converting types from Termwiz to Ratatui.
344///
345/// This trait replaces the `From` trait for converting types from Termwiz to Ratatui. It is
346/// necessary because the `From` trait is not implemented for types defined in external crates.
347pub trait FromTermwiz<T> {
348    /// Converts the given Termwiz type to the Ratatui type.
349    fn from_termwiz(termwiz: T) -> Self;
350}
351
352/// A trait for converting types from Ratatui to Termwiz.
353///
354/// This trait replaces the `Into` trait for converting types from Ratatui to Termwiz. It is
355/// necessary because the `Into` trait is not implemented for types defined in external crates.
356pub trait IntoTermwiz<T> {
357    /// Converts the given Ratatui type to the Termwiz type.
358    fn into_termwiz(self) -> T;
359}
360
361/// A replacement for the `Into` trait for converting types from Ratatui to Termwiz.
362///
363/// This trait is necessary because the `Into` trait is not implemented for types defined in
364/// external crates.
365///
366/// A blanket implementation is provided for all types that implement `FromTermwiz`.
367///
368/// This trait is private to the module as it would otherwise conflict with the other backend
369/// modules. It is mainly used to avoid rewriting all the `.into()` calls in this module.
370trait IntoRatatui<R> {
371    fn into_ratatui(self) -> R;
372}
373
374impl<C, R: FromTermwiz<C>> IntoRatatui<R> for C {
375    fn into_ratatui(self) -> R {
376        R::from_termwiz(self)
377    }
378}
379
380impl FromTermwiz<CellAttributes> for Style {
381    fn from_termwiz(value: CellAttributes) -> Self {
382        let mut style = Self::new()
383            .add_modifier(value.intensity().into_ratatui())
384            .add_modifier(value.underline().into_ratatui())
385            .add_modifier(value.blink().into_ratatui());
386
387        if value.italic() {
388            style.add_modifier |= Modifier::ITALIC;
389        }
390        if value.reverse() {
391            style.add_modifier |= Modifier::REVERSED;
392        }
393        if value.strikethrough() {
394            style.add_modifier |= Modifier::CROSSED_OUT;
395        }
396        if value.invisible() {
397            style.add_modifier |= Modifier::HIDDEN;
398        }
399
400        style.fg = Some(value.foreground().into_ratatui());
401        style.bg = Some(value.background().into_ratatui());
402        #[cfg(feature = "underline-color")]
403        {
404            style.underline_color = Some(value.underline_color().into_ratatui());
405        }
406
407        style
408    }
409}
410
411impl FromTermwiz<Intensity> for Modifier {
412    fn from_termwiz(value: Intensity) -> Self {
413        match value {
414            Intensity::Normal => Self::empty(),
415            Intensity::Bold => Self::BOLD,
416            Intensity::Half => Self::DIM,
417        }
418    }
419}
420
421impl FromTermwiz<Underline> for Modifier {
422    fn from_termwiz(value: Underline) -> Self {
423        match value {
424            Underline::None => Self::empty(),
425            _ => Self::UNDERLINED,
426        }
427    }
428}
429
430impl FromTermwiz<Blink> for Modifier {
431    fn from_termwiz(value: Blink) -> Self {
432        match value {
433            Blink::None => Self::empty(),
434            Blink::Slow => Self::SLOW_BLINK,
435            Blink::Rapid => Self::RAPID_BLINK,
436        }
437    }
438}
439
440impl IntoTermwiz<ColorAttribute> for Color {
441    fn into_termwiz(self) -> ColorAttribute {
442        match self {
443            Self::Reset => ColorAttribute::Default,
444            Self::Black => AnsiColor::Black.into(),
445            Self::DarkGray => AnsiColor::Grey.into(),
446            Self::Gray => AnsiColor::Silver.into(),
447            Self::Red => AnsiColor::Maroon.into(),
448            Self::LightRed => AnsiColor::Red.into(),
449            Self::Green => AnsiColor::Green.into(),
450            Self::LightGreen => AnsiColor::Lime.into(),
451            Self::Yellow => AnsiColor::Olive.into(),
452            Self::LightYellow => AnsiColor::Yellow.into(),
453            Self::Magenta => AnsiColor::Purple.into(),
454            Self::LightMagenta => AnsiColor::Fuchsia.into(),
455            Self::Cyan => AnsiColor::Teal.into(),
456            Self::LightCyan => AnsiColor::Aqua.into(),
457            Self::White => AnsiColor::White.into(),
458            Self::Blue => AnsiColor::Navy.into(),
459            Self::LightBlue => AnsiColor::Blue.into(),
460            Self::Indexed(i) => ColorAttribute::PaletteIndex(i),
461            Self::Rgb(r, g, b) => {
462                ColorAttribute::TrueColorWithDefaultFallback(SrgbaTuple::from((r, g, b)))
463            }
464        }
465    }
466}
467
468impl FromTermwiz<AnsiColor> for Color {
469    fn from_termwiz(value: AnsiColor) -> Self {
470        match value {
471            AnsiColor::Black => Self::Black,
472            AnsiColor::Grey => Self::DarkGray,
473            AnsiColor::Silver => Self::Gray,
474            AnsiColor::Maroon => Self::Red,
475            AnsiColor::Red => Self::LightRed,
476            AnsiColor::Green => Self::Green,
477            AnsiColor::Lime => Self::LightGreen,
478            AnsiColor::Olive => Self::Yellow,
479            AnsiColor::Yellow => Self::LightYellow,
480            AnsiColor::Purple => Self::Magenta,
481            AnsiColor::Fuchsia => Self::LightMagenta,
482            AnsiColor::Teal => Self::Cyan,
483            AnsiColor::Aqua => Self::LightCyan,
484            AnsiColor::White => Self::White,
485            AnsiColor::Navy => Self::Blue,
486            AnsiColor::Blue => Self::LightBlue,
487        }
488    }
489}
490
491impl FromTermwiz<ColorAttribute> for Color {
492    fn from_termwiz(value: ColorAttribute) -> Self {
493        match value {
494            ColorAttribute::TrueColorWithDefaultFallback(srgba)
495            | ColorAttribute::TrueColorWithPaletteFallback(srgba, _) => srgba.into_ratatui(),
496            ColorAttribute::PaletteIndex(i) => Self::Indexed(i),
497            ColorAttribute::Default => Self::Reset,
498        }
499    }
500}
501
502impl FromTermwiz<ColorSpec> for Color {
503    fn from_termwiz(value: ColorSpec) -> Self {
504        match value {
505            ColorSpec::Default => Self::Reset,
506            ColorSpec::PaletteIndex(i) => Self::Indexed(i),
507            ColorSpec::TrueColor(srgba) => srgba.into_ratatui(),
508        }
509    }
510}
511
512impl FromTermwiz<SrgbaTuple> for Color {
513    fn from_termwiz(value: SrgbaTuple) -> Self {
514        let (r, g, b, _) = value.to_srgb_u8();
515        Self::Rgb(r, g, b)
516    }
517}
518
519impl FromTermwiz<RgbColor> for Color {
520    fn from_termwiz(value: RgbColor) -> Self {
521        let (r, g, b) = value.to_tuple_rgb8();
522        Self::Rgb(r, g, b)
523    }
524}
525
526impl FromTermwiz<LinearRgba> for Color {
527    fn from_termwiz(value: LinearRgba) -> Self {
528        value.to_srgb().into_ratatui()
529    }
530}
531
532#[inline]
533fn u16_max(i: usize) -> u16 {
534    u16::try_from(i).unwrap_or(u16::MAX)
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    mod into_color {
542        use Color as C;
543
544        use super::*;
545
546        #[test]
547        fn from_linear_rgba() {
548            // full black + opaque
549            assert_eq!(
550                C::from_termwiz(LinearRgba(0., 0., 0., 1.)),
551                Color::Rgb(0, 0, 0)
552            );
553            // full black + transparent
554            assert_eq!(
555                C::from_termwiz(LinearRgba(0., 0., 0., 0.)),
556                Color::Rgb(0, 0, 0)
557            );
558
559            // full white + opaque
560            assert_eq!(
561                C::from_termwiz(LinearRgba(1., 1., 1., 1.)),
562                C::Rgb(254, 254, 254)
563            );
564            // full white + transparent
565            assert_eq!(
566                C::from_termwiz(LinearRgba(1., 1., 1., 0.)),
567                C::Rgb(254, 254, 254)
568            );
569
570            // full red
571            assert_eq!(
572                C::from_termwiz(LinearRgba(1., 0., 0., 1.)),
573                C::Rgb(254, 0, 0)
574            );
575            // full green
576            assert_eq!(
577                C::from_termwiz(LinearRgba(0., 1., 0., 1.)),
578                C::Rgb(0, 254, 0)
579            );
580            // full blue
581            assert_eq!(
582                C::from_termwiz(LinearRgba(0., 0., 1., 1.)),
583                C::Rgb(0, 0, 254)
584            );
585
586            // See https://stackoverflow.com/questions/12524623/what-are-the-practical-differences-when-working-with-colors-in-a-linear-vs-a-no
587            // for an explanation
588
589            // half red
590            assert_eq!(
591                C::from_termwiz(LinearRgba(0.214, 0., 0., 1.)),
592                C::Rgb(127, 0, 0)
593            );
594            // half green
595            assert_eq!(
596                C::from_termwiz(LinearRgba(0., 0.214, 0., 1.)),
597                C::Rgb(0, 127, 0)
598            );
599            // half blue
600            assert_eq!(
601                C::from_termwiz(LinearRgba(0., 0., 0.214, 1.)),
602                C::Rgb(0, 0, 127)
603            );
604        }
605
606        #[test]
607        fn from_srgba() {
608            // full black + opaque
609            assert_eq!(
610                C::from_termwiz(SrgbaTuple(0., 0., 0., 1.)),
611                Color::Rgb(0, 0, 0)
612            );
613            // full black + transparent
614            assert_eq!(
615                C::from_termwiz(SrgbaTuple(0., 0., 0., 0.)),
616                Color::Rgb(0, 0, 0)
617            );
618
619            // full white + opaque
620            assert_eq!(
621                C::from_termwiz(SrgbaTuple(1., 1., 1., 1.)),
622                C::Rgb(255, 255, 255)
623            );
624            // full white + transparent
625            assert_eq!(
626                C::from_termwiz(SrgbaTuple(1., 1., 1., 0.)),
627                C::Rgb(255, 255, 255)
628            );
629
630            // full red
631            assert_eq!(
632                C::from_termwiz(SrgbaTuple(1., 0., 0., 1.)),
633                C::Rgb(255, 0, 0)
634            );
635            // full green
636            assert_eq!(
637                C::from_termwiz(SrgbaTuple(0., 1., 0., 1.)),
638                C::Rgb(0, 255, 0)
639            );
640            // full blue
641            assert_eq!(
642                C::from_termwiz(SrgbaTuple(0., 0., 1., 1.)),
643                C::Rgb(0, 0, 255)
644            );
645
646            // half red
647            assert_eq!(
648                C::from_termwiz(SrgbaTuple(0.5, 0., 0., 1.)),
649                C::Rgb(127, 0, 0)
650            );
651            // half green
652            assert_eq!(
653                C::from_termwiz(SrgbaTuple(0., 0.5, 0., 1.)),
654                C::Rgb(0, 127, 0)
655            );
656            // half blue
657            assert_eq!(
658                C::from_termwiz(SrgbaTuple(0., 0., 0.5, 1.)),
659                C::Rgb(0, 0, 127)
660            );
661        }
662
663        #[test]
664        fn from_rgbcolor() {
665            // full black
666            assert_eq!(
667                C::from_termwiz(RgbColor::new_8bpc(0, 0, 0)),
668                Color::Rgb(0, 0, 0)
669            );
670            // full white
671            assert_eq!(
672                C::from_termwiz(RgbColor::new_8bpc(255, 255, 255)),
673                C::Rgb(255, 255, 255)
674            );
675
676            // full red
677            assert_eq!(
678                C::from_termwiz(RgbColor::new_8bpc(255, 0, 0)),
679                C::Rgb(255, 0, 0)
680            );
681            // full green
682            assert_eq!(
683                C::from_termwiz(RgbColor::new_8bpc(0, 255, 0)),
684                C::Rgb(0, 255, 0)
685            );
686            // full blue
687            assert_eq!(
688                C::from_termwiz(RgbColor::new_8bpc(0, 0, 255)),
689                C::Rgb(0, 0, 255)
690            );
691
692            // half red
693            assert_eq!(
694                C::from_termwiz(RgbColor::new_8bpc(127, 0, 0)),
695                C::Rgb(127, 0, 0)
696            );
697            // half green
698            assert_eq!(
699                C::from_termwiz(RgbColor::new_8bpc(0, 127, 0)),
700                C::Rgb(0, 127, 0)
701            );
702            // half blue
703            assert_eq!(
704                C::from_termwiz(RgbColor::new_8bpc(0, 0, 127)),
705                C::Rgb(0, 0, 127)
706            );
707        }
708
709        #[test]
710        fn from_colorspec() {
711            assert_eq!(C::from_termwiz(ColorSpec::Default), C::Reset);
712            assert_eq!(C::from_termwiz(ColorSpec::PaletteIndex(33)), C::Indexed(33));
713            assert_eq!(
714                C::from_termwiz(ColorSpec::TrueColor(SrgbaTuple(0., 0., 0., 1.))),
715                C::Rgb(0, 0, 0)
716            );
717        }
718
719        #[test]
720        fn from_colorattribute() {
721            assert_eq!(C::from_termwiz(ColorAttribute::Default), C::Reset);
722            assert_eq!(
723                C::from_termwiz(ColorAttribute::PaletteIndex(32)),
724                C::Indexed(32)
725            );
726            assert_eq!(
727                C::from_termwiz(ColorAttribute::TrueColorWithDefaultFallback(SrgbaTuple(
728                    0., 0., 0., 1.
729                ))),
730                C::Rgb(0, 0, 0)
731            );
732            assert_eq!(
733                C::from_termwiz(ColorAttribute::TrueColorWithPaletteFallback(
734                    SrgbaTuple(0., 0., 0., 1.),
735                    31
736                )),
737                C::Rgb(0, 0, 0)
738            );
739        }
740
741        #[test]
742        fn from_ansicolor() {
743            assert_eq!(C::from_termwiz(AnsiColor::Black), Color::Black);
744            assert_eq!(C::from_termwiz(AnsiColor::Grey), Color::DarkGray);
745            assert_eq!(C::from_termwiz(AnsiColor::Silver), Color::Gray);
746            assert_eq!(C::from_termwiz(AnsiColor::Maroon), Color::Red);
747            assert_eq!(C::from_termwiz(AnsiColor::Red), Color::LightRed);
748            assert_eq!(C::from_termwiz(AnsiColor::Green), Color::Green);
749            assert_eq!(C::from_termwiz(AnsiColor::Lime), Color::LightGreen);
750            assert_eq!(C::from_termwiz(AnsiColor::Olive), Color::Yellow);
751            assert_eq!(C::from_termwiz(AnsiColor::Yellow), Color::LightYellow);
752            assert_eq!(C::from_termwiz(AnsiColor::Purple), Color::Magenta);
753            assert_eq!(C::from_termwiz(AnsiColor::Fuchsia), Color::LightMagenta);
754            assert_eq!(C::from_termwiz(AnsiColor::Teal), Color::Cyan);
755            assert_eq!(C::from_termwiz(AnsiColor::Aqua), Color::LightCyan);
756            assert_eq!(C::from_termwiz(AnsiColor::White), Color::White);
757            assert_eq!(C::from_termwiz(AnsiColor::Navy), Color::Blue);
758            assert_eq!(C::from_termwiz(AnsiColor::Blue), Color::LightBlue);
759        }
760    }
761
762    mod into_modifier {
763        use super::*;
764
765        #[test]
766        fn from_intensity() {
767            assert_eq!(Modifier::from_termwiz(Intensity::Normal), Modifier::empty());
768            assert_eq!(Modifier::from_termwiz(Intensity::Bold), Modifier::BOLD);
769            assert_eq!(Modifier::from_termwiz(Intensity::Half), Modifier::DIM);
770        }
771
772        #[test]
773        fn from_underline() {
774            assert_eq!(Modifier::from_termwiz(Underline::None), Modifier::empty());
775            assert_eq!(
776                Modifier::from_termwiz(Underline::Single),
777                Modifier::UNDERLINED
778            );
779            assert_eq!(
780                Modifier::from_termwiz(Underline::Double),
781                Modifier::UNDERLINED
782            );
783            assert_eq!(
784                Modifier::from_termwiz(Underline::Curly),
785                Modifier::UNDERLINED
786            );
787            assert_eq!(
788                Modifier::from_termwiz(Underline::Dashed),
789                Modifier::UNDERLINED
790            );
791            assert_eq!(
792                Modifier::from_termwiz(Underline::Dotted),
793                Modifier::UNDERLINED
794            );
795        }
796
797        #[test]
798        fn from_blink() {
799            assert_eq!(Modifier::from_termwiz(Blink::None), Modifier::empty());
800            assert_eq!(Modifier::from_termwiz(Blink::Slow), Modifier::SLOW_BLINK);
801            assert_eq!(Modifier::from_termwiz(Blink::Rapid), Modifier::RAPID_BLINK);
802        }
803    }
804
805    #[test]
806    fn from_cell_attribute_for_style() {
807        #[cfg(feature = "underline-color")]
808        const STYLE: Style = Style::new()
809            .underline_color(Color::Reset)
810            .fg(Color::Reset)
811            .bg(Color::Reset);
812        #[cfg(not(feature = "underline-color"))]
813        const STYLE: Style = Style::new().fg(Color::Reset).bg(Color::Reset);
814
815        // default
816        assert_eq!(Style::from_termwiz(CellAttributes::default()), STYLE);
817
818        // foreground color
819        assert_eq!(
820            Style::from_termwiz(
821                CellAttributes::default()
822                    .set_foreground(ColorAttribute::PaletteIndex(31))
823                    .to_owned()
824            ),
825            STYLE.fg(Color::Indexed(31))
826        );
827        // background color
828        assert_eq!(
829            Style::from_termwiz(
830                CellAttributes::default()
831                    .set_background(ColorAttribute::PaletteIndex(31))
832                    .to_owned()
833            ),
834            STYLE.bg(Color::Indexed(31))
835        );
836        // underlined
837        assert_eq!(
838            Style::from_termwiz(
839                CellAttributes::default()
840                    .set_underline(Underline::Single)
841                    .to_owned()
842            ),
843            STYLE.underlined()
844        );
845        // blink
846        assert_eq!(
847            Style::from_termwiz(CellAttributes::default().set_blink(Blink::Slow).to_owned()),
848            STYLE.slow_blink()
849        );
850        // intensity
851        assert_eq!(
852            Style::from_termwiz(
853                CellAttributes::default()
854                    .set_intensity(Intensity::Bold)
855                    .to_owned()
856            ),
857            STYLE.bold()
858        );
859        // italic
860        assert_eq!(
861            Style::from_termwiz(CellAttributes::default().set_italic(true).to_owned()),
862            STYLE.italic()
863        );
864        // reversed
865        assert_eq!(
866            Style::from_termwiz(CellAttributes::default().set_reverse(true).to_owned()),
867            STYLE.reversed()
868        );
869        // strikethrough
870        assert_eq!(
871            Style::from_termwiz(CellAttributes::default().set_strikethrough(true).to_owned()),
872            STYLE.crossed_out()
873        );
874        // hidden
875        assert_eq!(
876            Style::from_termwiz(CellAttributes::default().set_invisible(true).to_owned()),
877            STYLE.hidden()
878        );
879
880        // underline color
881        #[cfg(feature = "underline-color")]
882        assert_eq!(
883            Style::from_termwiz(
884                CellAttributes::default()
885                    .set_underline_color(AnsiColor::Red)
886                    .to_owned()
887            ),
888            STYLE.underline_color(Color::Indexed(9))
889        );
890    }
891}