mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 13:20:41 +00:00
Add ANSI escape sequence parsing for enhanced log rendering
- Replace plain text rendering with `ansi_to_text` for displaying logs with styled ANSI sequences in TUI. - Implement parsing logic for SGR parameters to apply text styling (e.g., bold, italic, colors). - Extend TUI functionality to support dynamic styling based on ANSI codes. Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
e76f4c0278
commit
c0a7f7e3f2
1 changed files with 168 additions and 1 deletions
|
|
@ -714,7 +714,7 @@ impl TuiApp {
|
||||||
let text = if self.logs_text.is_empty() {
|
let text = if self.logs_text.is_empty() {
|
||||||
Text::from("No logs available.")
|
Text::from("No logs available.")
|
||||||
} else {
|
} else {
|
||||||
Text::from(self.logs_text.as_str())
|
ansi_to_text(&self.logs_text)
|
||||||
};
|
};
|
||||||
let paragraph = Paragraph::new(text)
|
let paragraph = Paragraph::new(text)
|
||||||
.block(Block::default().borders(Borders::ALL).title(title))
|
.block(Block::default().borders(Borders::ALL).title(title))
|
||||||
|
|
@ -737,3 +737,170 @@ impl TuiApp {
|
||||||
frame.render_widget(footer, area);
|
frame.render_widget(footer, area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ansi_to_text(input: &str) -> Text<'static> {
|
||||||
|
let bytes = input.as_bytes();
|
||||||
|
let mut i = 0;
|
||||||
|
let mut style = Style::default();
|
||||||
|
let mut buf = String::new();
|
||||||
|
let mut line: Vec<Span<'static>> = Vec::new();
|
||||||
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
|
|
||||||
|
let mut flush_buf = |buf: &mut String, line: &mut Vec<Span<'static>>, style: Style| {
|
||||||
|
if !buf.is_empty() {
|
||||||
|
let chunk = std::mem::take(buf);
|
||||||
|
line.push(Span::styled(chunk, style));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while i < bytes.len() {
|
||||||
|
if bytes[i] == 0x1b {
|
||||||
|
if i + 1 < bytes.len() && bytes[i + 1] == b'[' {
|
||||||
|
let mut j = i + 2;
|
||||||
|
while j < bytes.len() && !bytes[j].is_ascii_alphabetic() {
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if j < bytes.len() {
|
||||||
|
let cmd = bytes[j];
|
||||||
|
if cmd == b'm' {
|
||||||
|
flush_buf(&mut buf, &mut line, style);
|
||||||
|
let params = parse_sgr_params(&bytes[i + 2..j]);
|
||||||
|
apply_sgr(&mut style, ¶ms);
|
||||||
|
}
|
||||||
|
i = j + 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ch = input[i..].chars().next().unwrap_or('\0');
|
||||||
|
if ch == '\n' {
|
||||||
|
flush_buf(&mut buf, &mut line, style);
|
||||||
|
lines.push(Line::from(std::mem::take(&mut line)));
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch != '\r' {
|
||||||
|
buf.push(ch);
|
||||||
|
}
|
||||||
|
i += ch.len_utf8();
|
||||||
|
}
|
||||||
|
flush_buf(&mut buf, &mut line, style);
|
||||||
|
lines.push(Line::from(line));
|
||||||
|
Text::from(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_sgr_params(bytes: &[u8]) -> Vec<u16> {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let s = String::from_utf8_lossy(bytes);
|
||||||
|
s.split(';')
|
||||||
|
.filter_map(|p| p.parse::<u16>().ok())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_sgr(style: &mut Style, params: &[u16]) {
|
||||||
|
if params.is_empty() {
|
||||||
|
*style = Style::default();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut i = 0;
|
||||||
|
while i < params.len() {
|
||||||
|
match params[i] {
|
||||||
|
0 => *style = Style::default(),
|
||||||
|
1 => *style = style.add_modifier(Modifier::BOLD),
|
||||||
|
2 => *style = style.add_modifier(Modifier::DIM),
|
||||||
|
3 => *style = style.add_modifier(Modifier::ITALIC),
|
||||||
|
4 => *style = style.add_modifier(Modifier::UNDERLINED),
|
||||||
|
7 => *style = style.add_modifier(Modifier::REVERSED),
|
||||||
|
22 => *style = style.remove_modifier(Modifier::BOLD | Modifier::DIM),
|
||||||
|
23 => *style = style.remove_modifier(Modifier::ITALIC),
|
||||||
|
24 => *style = style.remove_modifier(Modifier::UNDERLINED),
|
||||||
|
27 => *style = style.remove_modifier(Modifier::REVERSED),
|
||||||
|
30..=37 => {
|
||||||
|
*style = style.fg(sgr_basic_color(params[i] - 30));
|
||||||
|
}
|
||||||
|
40..=47 => {
|
||||||
|
*style = style.bg(sgr_basic_color(params[i] - 40));
|
||||||
|
}
|
||||||
|
90..=97 => {
|
||||||
|
*style = style.fg(sgr_bright_color(params[i] - 90));
|
||||||
|
}
|
||||||
|
100..=107 => {
|
||||||
|
*style = style.bg(sgr_bright_color(params[i] - 100));
|
||||||
|
}
|
||||||
|
38 => {
|
||||||
|
if i + 1 < params.len() {
|
||||||
|
match params[i + 1] {
|
||||||
|
5 if i + 2 < params.len() => {
|
||||||
|
*style = style.fg(Color::Indexed(params[i + 2] as u8));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
2 if i + 4 < params.len() => {
|
||||||
|
*style = style.fg(Color::Rgb(
|
||||||
|
params[i + 2] as u8,
|
||||||
|
params[i + 3] as u8,
|
||||||
|
params[i + 4] as u8,
|
||||||
|
));
|
||||||
|
i += 4;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
48 => {
|
||||||
|
if i + 1 < params.len() {
|
||||||
|
match params[i + 1] {
|
||||||
|
5 if i + 2 < params.len() => {
|
||||||
|
*style = style.bg(Color::Indexed(params[i + 2] as u8));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
2 if i + 4 < params.len() => {
|
||||||
|
*style = style.bg(Color::Rgb(
|
||||||
|
params[i + 2] as u8,
|
||||||
|
params[i + 3] as u8,
|
||||||
|
params[i + 4] as u8,
|
||||||
|
));
|
||||||
|
i += 4;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
39 => *style = style.fg(Color::Reset),
|
||||||
|
49 => *style = style.bg(Color::Reset),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sgr_basic_color(idx: u16) -> Color {
|
||||||
|
match idx {
|
||||||
|
0 => Color::Black,
|
||||||
|
1 => Color::Red,
|
||||||
|
2 => Color::Green,
|
||||||
|
3 => Color::Yellow,
|
||||||
|
4 => Color::Blue,
|
||||||
|
5 => Color::Magenta,
|
||||||
|
6 => Color::Cyan,
|
||||||
|
7 => Color::White,
|
||||||
|
_ => Color::Reset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sgr_bright_color(idx: u16) -> Color {
|
||||||
|
match idx {
|
||||||
|
0 => Color::DarkGray,
|
||||||
|
1 => Color::LightRed,
|
||||||
|
2 => Color::LightGreen,
|
||||||
|
3 => Color::LightYellow,
|
||||||
|
4 => Color::LightBlue,
|
||||||
|
5 => Color::LightMagenta,
|
||||||
|
6 => Color::LightCyan,
|
||||||
|
7 => Color::White,
|
||||||
|
_ => Color::Reset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue