ratatui_termwiz/
lib.rs

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