@@ -94,6 +94,160 @@ namespace pythonic
9494 return false ;
9595 }
9696
97+ /* *
98+ * @brief Parse ANSI 256-color escape sequence
99+ * @param ansi ANSI escape sequence like "\033[38;5;XXXm" or combined "\033[38;5;FG;48;5;BGm"
100+ * @param r Output red component
101+ * @param g Output green component
102+ * @param b Output blue component
103+ * @param is_foreground Output: true if this is a foreground color
104+ * @return true if parsed successfully
105+ */
106+ inline bool parse_ansi_256 (const std::string &ansi, uint8_t &r, uint8_t &g, uint8_t &b, bool &is_foreground)
107+ {
108+ // Convert color index to RGB
109+ auto color_to_rgb = [](int color_index, uint8_t &r, uint8_t &g, uint8_t &b) -> bool
110+ {
111+ // Standard colors (0-15): Use basic color mapping
112+ if (color_index < 16 )
113+ {
114+ static const uint8_t basic_colors[16 ][3 ] = {
115+ {0 , 0 , 0 }, // 0 Black
116+ {128 , 0 , 0 }, // 1 Red
117+ {0 , 128 , 0 }, // 2 Green
118+ {128 , 128 , 0 }, // 3 Yellow
119+ {0 , 0 , 128 }, // 4 Blue
120+ {128 , 0 , 128 }, // 5 Magenta
121+ {0 , 128 , 128 }, // 6 Cyan
122+ {192 , 192 , 192 }, // 7 White
123+ {128 , 128 , 128 }, // 8 Bright Black
124+ {255 , 0 , 0 }, // 9 Bright Red
125+ {0 , 255 , 0 }, // 10 Bright Green
126+ {255 , 255 , 0 }, // 11 Bright Yellow
127+ {0 , 0 , 255 }, // 12 Bright Blue
128+ {255 , 0 , 255 }, // 13 Bright Magenta
129+ {0 , 255 , 255 }, // 14 Bright Cyan
130+ {255 , 255 , 255 } // 15 Bright White
131+ };
132+ r = basic_colors[color_index][0 ];
133+ g = basic_colors[color_index][1 ];
134+ b = basic_colors[color_index][2 ];
135+ return true ;
136+ }
137+ // Color cube (16-231): 6x6x6 RGB
138+ else if (color_index < 232 )
139+ {
140+ int idx = color_index - 16 ;
141+ int ri = idx / 36 ;
142+ int gi = (idx % 36 ) / 6 ;
143+ int bi = idx % 6 ;
144+ r = ri ? (ri * 40 + 55 ) : 0 ;
145+ g = gi ? (gi * 40 + 55 ) : 0 ;
146+ b = bi ? (bi * 40 + 55 ) : 0 ;
147+ return true ;
148+ }
149+ // Grayscale (232-255): 24 shades
150+ else if (color_index < 256 )
151+ {
152+ int gray = (color_index - 232 ) * 10 + 8 ;
153+ r = g = b = static_cast <uint8_t >(gray);
154+ return true ;
155+ }
156+ return false ;
157+ };
158+
159+ // Match ESC[38;5;Nm (foreground only)
160+ std::regex fg_only_regex (R"( \x1b\[38;5;(\d+)m)" );
161+ std::smatch match;
162+ if (std::regex_search (ansi, match, fg_only_regex) && match.prefix ().str ().empty ())
163+ {
164+ int color_index = std::stoi (match[1 ].str ());
165+ is_foreground = true ;
166+ return color_to_rgb (color_index, r, g, b);
167+ }
168+
169+ // Match ESC[48;5;Nm (background only)
170+ std::regex bg_only_regex (R"( \x1b\[48;5;(\d+)m)" );
171+ if (std::regex_search (ansi, match, bg_only_regex) && match.prefix ().str ().empty ())
172+ {
173+ int color_index = std::stoi (match[1 ].str ());
174+ is_foreground = false ;
175+ return color_to_rgb (color_index, r, g, b);
176+ }
177+
178+ return false ;
179+ }
180+
181+ /* *
182+ * @brief Parse combined ANSI 256-color escape sequence with both fg and bg
183+ * @param ansi ANSI escape sequence like "\033[38;5;FG;48;5;BGm"
184+ * @param fg_r, fg_g, fg_b Output foreground RGB
185+ * @param bg_r, bg_g, bg_b Output background RGB
186+ * @param has_fg, has_bg Output: which colors were found
187+ * @return true if parsed successfully
188+ */
189+ inline bool parse_ansi_256_combined (const std::string &ansi,
190+ uint8_t &fg_r, uint8_t &fg_g, uint8_t &fg_b,
191+ uint8_t &bg_r, uint8_t &bg_g, uint8_t &bg_b,
192+ bool &has_fg, bool &has_bg)
193+ {
194+ has_fg = has_bg = false ;
195+
196+ // Convert color index to RGB
197+ auto color_to_rgb = [](int color_index, uint8_t &r, uint8_t &g, uint8_t &b) -> bool
198+ {
199+ if (color_index < 16 )
200+ {
201+ static const uint8_t basic_colors[16 ][3 ] = {
202+ {0 , 0 , 0 }, {128 , 0 , 0 }, {0 , 128 , 0 }, {128 , 128 , 0 }, {0 , 0 , 128 }, {128 , 0 , 128 }, {0 , 128 , 128 }, {192 , 192 , 192 }, {128 , 128 , 128 }, {255 , 0 , 0 }, {0 , 255 , 0 }, {255 , 255 , 0 }, {0 , 0 , 255 }, {255 , 0 , 255 }, {0 , 255 , 255 }, {255 , 255 , 255 }};
203+ r = basic_colors[color_index][0 ];
204+ g = basic_colors[color_index][1 ];
205+ b = basic_colors[color_index][2 ];
206+ return true ;
207+ }
208+ else if (color_index < 232 )
209+ {
210+ int idx = color_index - 16 ;
211+ r = (idx / 36 ) ? ((idx / 36 ) * 40 + 55 ) : 0 ;
212+ g = ((idx % 36 ) / 6 ) ? (((idx % 36 ) / 6 ) * 40 + 55 ) : 0 ;
213+ b = (idx % 6 ) ? ((idx % 6 ) * 40 + 55 ) : 0 ;
214+ return true ;
215+ }
216+ else if (color_index < 256 )
217+ {
218+ int gray = (color_index - 232 ) * 10 + 8 ;
219+ r = g = b = static_cast <uint8_t >(gray);
220+ return true ;
221+ }
222+ return false ;
223+ };
224+
225+ // Match combined fg+bg: ESC[38;5;FG;48;5;BGm
226+ std::regex combined_regex (R"( \x1b\[38;5;(\d+);48;5;(\d+)m)" );
227+ std::smatch match;
228+ if (std::regex_search (ansi, match, combined_regex))
229+ {
230+ int fg_idx = std::stoi (match[1 ].str ());
231+ int bg_idx = std::stoi (match[2 ].str ());
232+ has_fg = color_to_rgb (fg_idx, fg_r, fg_g, fg_b);
233+ has_bg = color_to_rgb (bg_idx, bg_r, bg_g, bg_b);
234+ return has_fg || has_bg;
235+ }
236+
237+ // Also match reverse order: ESC[48;5;BG;38;5;FGm
238+ std::regex combined_rev_regex (R"( \x1b\[48;5;(\d+);38;5;(\d+)m)" );
239+ if (std::regex_search (ansi, match, combined_rev_regex))
240+ {
241+ int bg_idx = std::stoi (match[1 ].str ());
242+ int fg_idx = std::stoi (match[2 ].str ());
243+ has_fg = color_to_rgb (fg_idx, fg_r, fg_g, fg_b);
244+ has_bg = color_to_rgb (bg_idx, bg_r, bg_g, bg_b);
245+ return has_fg || has_bg;
246+ }
247+
248+ return false ;
249+ }
250+
97251 /* *
98252 * @brief Strip all ANSI escape codes from a string
99253 */
@@ -346,7 +500,7 @@ namespace pythonic
346500
347501 std::string escape = line.substr (start, i - start);
348502
349- // Parse RGB color
503+ // Try parsing 24-bit RGB color first
350504 uint8_t r, g, b;
351505 if (parse_ansi_rgb (escape, r, g, b))
352506 {
@@ -361,14 +515,51 @@ namespace pythonic
361515 has_bg = true ;
362516 }
363517 }
364- // Reset code
365- else if (escape.find (" [0m" ) != std::string::npos ||
366- escape.find (" [m" ) != std::string::npos)
518+ // Try parsing combined 256-color (fg+bg in one escape)
519+ else
367520 {
368- current_fg = RGB (255 , 255 , 255 );
369- current_bg = RGB (0 , 0 , 0 );
370- has_fg = false ;
371- has_bg = false ;
521+ uint8_t fg_r, fg_g, fg_b, bg_r, bg_g, bg_b;
522+ bool got_fg, got_bg;
523+ if (parse_ansi_256_combined (escape, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, got_fg, got_bg))
524+ {
525+ if (got_fg)
526+ {
527+ current_fg = RGB (fg_r, fg_g, fg_b);
528+ has_fg = true ;
529+ }
530+ if (got_bg)
531+ {
532+ current_bg = RGB (bg_r, bg_g, bg_b);
533+ has_bg = true ;
534+ }
535+ }
536+ // Try parsing single 256-color
537+ else
538+ {
539+ bool is_foreground;
540+ if (parse_ansi_256 (escape, r, g, b, is_foreground))
541+ {
542+ if (is_foreground)
543+ {
544+ current_fg = RGB (r, g, b);
545+ has_fg = true ;
546+ }
547+ else
548+ {
549+ current_bg = RGB (r, g, b);
550+ has_bg = true ;
551+ }
552+ }
553+ // Reset code
554+ else if (escape.find (" [0m" ) != std::string::npos ||
555+ escape.find (" [m" ) != std::string::npos)
556+ {
557+ current_fg = RGB (255 , 255 , 255 );
558+ current_bg = RGB (0 , 0 , 0 );
559+ has_fg = false ;
560+ has_bg = false ;
561+ }
562+ }
372563 }
373564 continue ;
374565 }
0 commit comments