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