1use crate::ansi::{color_to_ansi, emphasis_to_ansi};
9use crate::lexer::{TagType, Token};
10
11pub fn render(tokens: Vec<Token>) -> String {
25 let mut result = String::new();
26 let mut active: Vec<TagType> = Vec::new();
27 for t in tokens {
28 match t {
29 Token::Text(s) | Token::Tag(TagType::Prefix(s)) => result.push_str(&s),
30 Token::Tag(TagType::Color { color, ground }) => {
31 result.push_str(&color_to_ansi(&color, ground.clone()));
32 active.push(TagType::Color { color, ground });
33 }
34 Token::Tag(TagType::Emphasis(e)) => {
35 result.push_str(&emphasis_to_ansi(&e));
36 active.push(TagType::Emphasis(e));
37 }
38 Token::Tag(TagType::Reset(None)) => {
39 result.push_str("\x1b[0m");
40 active.clear();
41 }
42 Token::Tag(TagType::Reset(Some(r))) => {
43 result.push_str("\x1b[0m");
44 active.retain(|x| x != r.as_ref());
45 for a in &active {
46 match a {
47 TagType::Color { color, ground } => {
48 result.push_str(&color_to_ansi(color, ground.clone()))
49 }
50 TagType::Emphasis(e) => result.push_str(&emphasis_to_ansi(e)),
51 _ => {}
52 }
53 }
54 }
55 }
56 }
57 result
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::ansi::{Color, Ground, NamedColor};
64 use crate::lexer::{EmphasisType, TagType, Token};
65
66 #[test]
68 fn test_render_empty_token_list() {
69 let result = render(vec![]);
70 assert_eq!(result, "");
71 }
72 #[test]
73 fn test_render_plain_text_token() {
74 let result = render(vec![Token::Text("hello".into())]);
75 assert_eq!(result, "hello");
76 }
77 #[test]
78 fn test_render_named_color_tag() {
79 let result = render(vec![Token::Tag(TagType::Color {
80 color: Color::Named(NamedColor::Red),
81 ground: Ground::Foreground,
82 })]);
83 assert_eq!(result, "\x1b[31m");
84 }
85 #[test]
86 fn test_render_emphasis_tag_bold() {
87 let result = render(vec![Token::Tag(TagType::Emphasis(EmphasisType::Bold))]);
88 assert_eq!(result, "\x1b[1m");
89 }
90 #[test]
91 fn test_render_reset_tag() {
92 let result = render(vec![Token::Tag(TagType::Reset(None))]);
93 assert_eq!(result, "\x1b[0m");
94 }
95 #[test]
96 fn test_render_color_then_text() {
97 let result = render(vec![
98 Token::Tag(TagType::Color {
99 color: Color::Named(NamedColor::Red),
100 ground: Ground::Foreground,
101 }),
102 Token::Text("hello".into()),
103 ]);
104 assert_eq!(result, "\x1b[31mhello");
105 }
106 #[test]
107 fn test_render_color_text_reset() {
108 let result = render(vec![
109 Token::Tag(TagType::Color {
110 color: Color::Named(NamedColor::Green),
111 ground: Ground::Foreground,
112 }),
113 Token::Text("go".into()),
114 Token::Tag(TagType::Reset(None)),
115 ]);
116 assert_eq!(result, "\x1b[32mgo\x1b[0m");
117 }
118 #[test]
119 fn test_render_multiple_text_tokens() {
120 let result = render(vec![Token::Text("foo".into()), Token::Text("bar".into())]);
121 assert_eq!(result, "foobar");
122 }
123 #[test]
124 fn test_render_ansi256_color_tag() {
125 let result = render(vec![Token::Tag(TagType::Color {
126 color: Color::Ansi256(21),
127 ground: Ground::Foreground,
128 })]);
129 assert_eq!(result, "\x1b[38;5;21m");
130 }
131 #[test]
132 fn test_render_rgb_color_tag() {
133 let result = render(vec![Token::Tag(TagType::Color {
134 color: Color::Rgb(255, 0, 0),
135 ground: Ground::Foreground,
136 })]);
137 assert_eq!(result, "\x1b[38;2;255;0;0m");
138 }
139 #[test]
140 fn test_render_does_not_append_trailing_reset() {
141 let result = render(vec![Token::Text("plain".into())]);
142 assert!(!result.ends_with("\x1b[0m"));
143 }
144 #[test]
145 fn test_render_named_color_background() {
146 let result = render(vec![Token::Tag(TagType::Color {
147 color: Color::Named(NamedColor::Red),
148 ground: Ground::Background,
149 })]);
150 assert_eq!(result, "\x1b[41m");
151 }
152 #[test]
153 fn test_render_ansi256_background() {
154 let result = render(vec![Token::Tag(TagType::Color {
155 color: Color::Ansi256(21),
156 ground: Ground::Background,
157 })]);
158 assert_eq!(result, "\x1b[48;5;21m");
159 }
160 #[test]
161 fn test_render_rgb_background() {
162 let result = render(vec![Token::Tag(TagType::Color {
163 color: Color::Rgb(255, 0, 0),
164 ground: Ground::Background,
165 })]);
166 assert_eq!(result, "\x1b[48;2;255;0;0m");
167 }
168 #[test]
169 fn test_render_fg_and_bg_together() {
170 let result = render(vec![
171 Token::Tag(TagType::Color {
172 color: Color::Named(NamedColor::White),
173 ground: Ground::Foreground,
174 }),
175 Token::Tag(TagType::Color {
176 color: Color::Named(NamedColor::Blue),
177 ground: Ground::Background,
178 }),
179 Token::Text("hello".into()),
180 ]);
181 assert_eq!(result, "\x1b[37m\x1b[44mhello");
182 }
183}
184