urushi_adapter_ratatui/
style.rs1use ::ratatui::style::{Color as RatatuiColor, Modifier as RatatuiModifier, Style as InnerStyle};
4
5use urushi::{Color, TerminalTextStyle, TextAttribute, TextAttributes, TextStyle};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct RatatuiStyle {
20 content: InnerStyle,
21}
22
23impl RatatuiStyle {
24 pub const fn into_inner(self) -> InnerStyle {
25 self.content
26 }
27}
28
29impl From<&TextStyle> for RatatuiStyle {
30 fn from(value: &TextStyle) -> Self {
31 let terminal = TerminalTextStyle::from(value).style();
32 let mut style = InnerStyle::new();
33 if let Some(color) = ratatui_color(terminal.foreground) {
34 style = style.fg(color);
35 }
36 if let Some(color) = ratatui_color(terminal.background) {
37 style = style.bg(color);
38 }
39 if terminal.underline.is_some() {
45 style = style.add_modifier(RatatuiModifier::UNDERLINED);
46 }
47 Self {
48 content: style.add_modifier(ratatui_attributes(terminal.attributes)),
49 }
50 }
51}
52
53fn ratatui_attributes(attributes: TextAttributes) -> RatatuiModifier {
54 let mut converted = RatatuiModifier::empty();
55 for attribute in attributes {
56 converted = converted.union(match attribute {
57 TextAttribute::Bold => RatatuiModifier::BOLD,
58 TextAttribute::Dim => RatatuiModifier::DIM,
59 TextAttribute::Italic => RatatuiModifier::ITALIC,
60 TextAttribute::SlowBlink => RatatuiModifier::SLOW_BLINK,
61 TextAttribute::RapidBlink => RatatuiModifier::RAPID_BLINK,
62 TextAttribute::Reversed => RatatuiModifier::REVERSED,
63 TextAttribute::Hidden => RatatuiModifier::HIDDEN,
64 TextAttribute::CrossedOut => RatatuiModifier::CROSSED_OUT,
65 TextAttribute::Fraktur
66 | TextAttribute::Framed
67 | TextAttribute::Encircled
68 | TextAttribute::Overlined => RatatuiModifier::empty(),
69 });
70 }
71 converted
72}
73
74impl From<RatatuiStyle> for InnerStyle {
75 fn from(value: RatatuiStyle) -> Self {
76 value.into_inner()
77 }
78}
79
80const fn ratatui_color(color: Option<Color>) -> Option<RatatuiColor> {
81 match color {
82 None => None,
83 Some(Color::Ansi(0)) => Some(RatatuiColor::Black),
84 Some(Color::Ansi(1)) => Some(RatatuiColor::Red),
85 Some(Color::Ansi(2)) => Some(RatatuiColor::Green),
86 Some(Color::Ansi(3)) => Some(RatatuiColor::Yellow),
87 Some(Color::Ansi(4)) => Some(RatatuiColor::Blue),
88 Some(Color::Ansi(5)) => Some(RatatuiColor::Magenta),
89 Some(Color::Ansi(6)) => Some(RatatuiColor::Cyan),
90 Some(Color::Ansi(7)) => Some(RatatuiColor::Gray),
91 Some(Color::Ansi(8)) => Some(RatatuiColor::DarkGray),
92 Some(Color::Ansi(9)) => Some(RatatuiColor::LightRed),
93 Some(Color::Ansi(10)) => Some(RatatuiColor::LightGreen),
94 Some(Color::Ansi(11)) => Some(RatatuiColor::LightYellow),
95 Some(Color::Ansi(12)) => Some(RatatuiColor::LightBlue),
96 Some(Color::Ansi(13)) => Some(RatatuiColor::LightMagenta),
97 Some(Color::Ansi(14)) => Some(RatatuiColor::LightCyan),
98 Some(Color::Ansi(15)) => Some(RatatuiColor::White),
99 Some(Color::Ansi(index) | Color::Ansi256(index)) => Some(RatatuiColor::Indexed(index)),
100 Some(Color::Rgb(red, green, blue)) => Some(RatatuiColor::Rgb(red, green, blue)),
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use urushi::{Color, TextAttribute, UnderlineStyle};
107
108 use super::*;
109
110 #[test]
111 fn converts_active_attributes() {
112 let converted = RatatuiStyle::from(
113 &TextStyle::new()
114 .add_attributes(TextAttribute::Bold | TextAttribute::Italic)
115 .remove_attribute(TextAttribute::Italic),
116 )
117 .into_inner();
118
119 assert_eq!(converted.add_modifier, RatatuiModifier::BOLD);
120 assert!(converted.sub_modifier.is_empty());
121 }
122
123 #[test]
124 fn every_underline_degrades_to_the_plain_underlined_modifier() {
125 let styles = [
129 TextStyle::new().underlined(),
130 TextStyle::new().underline_style(UnderlineStyle::Double),
131 TextStyle::new().underline_style(UnderlineStyle::Curly),
132 TextStyle::new().underline_style(UnderlineStyle::Dotted),
133 TextStyle::new().underline_style(UnderlineStyle::Dashed),
134 TextStyle::new().underline_color(Color::RED),
135 ];
136
137 for style in styles {
138 let converted = RatatuiStyle::from(&style).into_inner();
139
140 assert_eq!(converted.add_modifier, RatatuiModifier::UNDERLINED);
141 assert_eq!(converted.fg, None);
142 }
143
144 assert!(
145 RatatuiStyle::from(&TextStyle::new())
146 .into_inner()
147 .add_modifier
148 .is_empty()
149 );
150 }
151
152 #[test]
153 fn hyperlink_is_intentionally_lost_at_the_cell_style_boundary() {
154 assert_eq!(
155 RatatuiStyle::from(&TextStyle::new().hyperlink("https://example.com")),
156 RatatuiStyle::from(&TextStyle::new())
157 );
158 }
159}