1#![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#![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
32pub struct TermwizBackend {
71 buffered_terminal: BufferedTerminal<SystemTerminal>,
72}
73
74impl TermwizBackend {
75 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 pub const fn with_buffered_terminal(instance: BufferedTerminal<SystemTerminal>) -> Self {
105 Self {
106 buffered_terminal: instance,
107 }
108 }
109
110 pub const fn buffered_terminal(&self) -> &BufferedTerminal<SystemTerminal> {
112 &self.buffered_terminal
113 }
114
115 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 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 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
319pub trait FromTermwiz<T> {
324 fn from_termwiz(termwiz: T) -> Self;
326}
327
328pub trait IntoTermwiz<T> {
333 fn into_termwiz(self) -> T;
335}
336
337trait 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 assert_eq!(
526 C::from_termwiz(LinearRgba(0., 0., 0., 1.)),
527 Color::Rgb(0, 0, 0)
528 );
529 assert_eq!(
531 C::from_termwiz(LinearRgba(0., 0., 0., 0.)),
532 Color::Rgb(0, 0, 0)
533 );
534
535 assert_eq!(
537 C::from_termwiz(LinearRgba(1., 1., 1., 1.)),
538 C::Rgb(254, 254, 254)
539 );
540 assert_eq!(
542 C::from_termwiz(LinearRgba(1., 1., 1., 0.)),
543 C::Rgb(254, 254, 254)
544 );
545
546 assert_eq!(
548 C::from_termwiz(LinearRgba(1., 0., 0., 1.)),
549 C::Rgb(254, 0, 0)
550 );
551 assert_eq!(
553 C::from_termwiz(LinearRgba(0., 1., 0., 1.)),
554 C::Rgb(0, 254, 0)
555 );
556 assert_eq!(
558 C::from_termwiz(LinearRgba(0., 0., 1., 1.)),
559 C::Rgb(0, 0, 254)
560 );
561
562 assert_eq!(
567 C::from_termwiz(LinearRgba(0.214, 0., 0., 1.)),
568 C::Rgb(127, 0, 0)
569 );
570 assert_eq!(
572 C::from_termwiz(LinearRgba(0., 0.214, 0., 1.)),
573 C::Rgb(0, 127, 0)
574 );
575 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 assert_eq!(
586 C::from_termwiz(SrgbaTuple(0., 0., 0., 1.)),
587 Color::Rgb(0, 0, 0)
588 );
589 assert_eq!(
591 C::from_termwiz(SrgbaTuple(0., 0., 0., 0.)),
592 Color::Rgb(0, 0, 0)
593 );
594
595 assert_eq!(
597 C::from_termwiz(SrgbaTuple(1., 1., 1., 1.)),
598 C::Rgb(255, 255, 255)
599 );
600 assert_eq!(
602 C::from_termwiz(SrgbaTuple(1., 1., 1., 0.)),
603 C::Rgb(255, 255, 255)
604 );
605
606 assert_eq!(
608 C::from_termwiz(SrgbaTuple(1., 0., 0., 1.)),
609 C::Rgb(255, 0, 0)
610 );
611 assert_eq!(
613 C::from_termwiz(SrgbaTuple(0., 1., 0., 1.)),
614 C::Rgb(0, 255, 0)
615 );
616 assert_eq!(
618 C::from_termwiz(SrgbaTuple(0., 0., 1., 1.)),
619 C::Rgb(0, 0, 255)
620 );
621
622 assert_eq!(
624 C::from_termwiz(SrgbaTuple(0.5, 0., 0., 1.)),
625 C::Rgb(127, 0, 0)
626 );
627 assert_eq!(
629 C::from_termwiz(SrgbaTuple(0., 0.5, 0., 1.)),
630 C::Rgb(0, 127, 0)
631 );
632 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 assert_eq!(
643 C::from_termwiz(RgbColor::new_8bpc(0, 0, 0)),
644 Color::Rgb(0, 0, 0)
645 );
646 assert_eq!(
648 C::from_termwiz(RgbColor::new_8bpc(255, 255, 255)),
649 C::Rgb(255, 255, 255)
650 );
651
652 assert_eq!(
654 C::from_termwiz(RgbColor::new_8bpc(255, 0, 0)),
655 C::Rgb(255, 0, 0)
656 );
657 assert_eq!(
659 C::from_termwiz(RgbColor::new_8bpc(0, 255, 0)),
660 C::Rgb(0, 255, 0)
661 );
662 assert_eq!(
664 C::from_termwiz(RgbColor::new_8bpc(0, 0, 255)),
665 C::Rgb(0, 0, 255)
666 );
667
668 assert_eq!(
670 C::from_termwiz(RgbColor::new_8bpc(127, 0, 0)),
671 C::Rgb(127, 0, 0)
672 );
673 assert_eq!(
675 C::from_termwiz(RgbColor::new_8bpc(0, 127, 0)),
676 C::Rgb(0, 127, 0)
677 );
678 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 assert_eq!(Style::from_termwiz(CellAttributes::default()), STYLE);
793
794 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 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 assert_eq!(
814 Style::from_termwiz(
815 CellAttributes::default()
816 .set_underline(Underline::Single)
817 .to_owned()
818 ),
819 STYLE.underlined()
820 );
821 assert_eq!(
823 Style::from_termwiz(CellAttributes::default().set_blink(Blink::Slow).to_owned()),
824 STYLE.slow_blink()
825 );
826 assert_eq!(
828 Style::from_termwiz(
829 CellAttributes::default()
830 .set_intensity(Intensity::Bold)
831 .to_owned()
832 ),
833 STYLE.bold()
834 );
835 assert_eq!(
837 Style::from_termwiz(CellAttributes::default().set_italic(true).to_owned()),
838 STYLE.italic()
839 );
840 assert_eq!(
842 Style::from_termwiz(CellAttributes::default().set_reverse(true).to_owned()),
843 STYLE.reversed()
844 );
845 assert_eq!(
847 Style::from_termwiz(CellAttributes::default().set_strikethrough(true).to_owned()),
848 STYLE.crossed_out()
849 );
850 assert_eq!(
852 Style::from_termwiz(CellAttributes::default().set_invisible(true).to_owned()),
853 STYLE.hidden()
854 );
855
856 #[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}