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() {
|
||||
Text::from("No logs available.")
|
||||
} else {
|
||||
Text::from(self.logs_text.as_str())
|
||||
ansi_to_text(&self.logs_text)
|
||||
};
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL).title(title))
|
||||
|
|
@ -737,3 +737,170 @@ impl TuiApp {
|
|||
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