use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use seer_core::output::sanitize_line;
use crate::tui::theme::Theme;
use crate::tui::widgets::wrap_hard;
pub fn gutter<'a>(keys: impl IntoIterator<Item = &'a str>, width: u16) -> usize {
let longest = keys
.into_iter()
.map(|k| Span::raw(k).width())
.max()
.unwrap_or(0);
(longest + 4).min(usize::from(width) / 2)
}
pub fn leader(theme: &Theme, key: &str, key_color: Color, gutter: usize) -> [Span<'static>; 2] {
let dots = gutter.saturating_sub(Span::raw(key).width() + 2).max(1);
[
Span::styled(key.to_string(), Style::default().fg(key_color)),
Span::styled(
format!(" {} ", "·".repeat(dots)),
Style::default().fg(theme.surface1),
),
]
}
pub fn lines(
theme: &Theme,
key_color: Color,
rows: &[(&str, String)],
width: u16,
) -> Vec<Line<'static>> {
let gutter = gutter(rows.iter().map(|(k, _)| *k), width);
let value_w = u16::try_from(usize::from(width).saturating_sub(gutter)).unwrap_or(0);
let mut out = Vec::new();
for (k, v) in rows {
let mut chunks = wrap_hard(&sanitize_line(v), value_w);
if chunks.is_empty() {
chunks.push(String::new());
}
for (i, chunk) in chunks.into_iter().enumerate() {
let mut spans = if i == 0 {
leader(theme, k, key_color, gutter).to_vec()
} else {
vec![Span::raw(" ".repeat(gutter))]
};
spans.push(Span::styled(chunk, Style::default().fg(theme.text)));
out.push(Line::from(spans));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tui::test_util::render_lines;
use ratatui::widgets::Paragraph;
#[test]
fn leaders_stop_at_a_shared_gutter() {
let theme = Theme::frappe();
let rows = [
("registrar", "IANA".to_string()),
("expires", "2027-08-13".to_string()),
];
let s = render_lines(80, 2, |f| {
let lines = lines(&theme, theme.blue, &rows, f.area().width);
f.render_widget(Paragraph::new(lines), f.area());
});
let lines: Vec<&str> = s.lines().collect();
assert!(lines[0].starts_with("registrar ·· IANA"), "{s}");
assert!(lines[1].starts_with("expires ···· 2027-08-13"), "{s}");
}
#[test]
fn long_values_wrap_under_the_value_column_sanitized() {
let theme = Theme::frappe();
let rows = [("ns", format!("{}\x1b[2J", "a".repeat(30)))];
let got = lines(&theme, theme.blue, &rows, 20);
assert!(got.len() > 1, "{got:?}");
let text: String = got
.iter()
.flat_map(|l| l.spans.iter().map(|s| s.content.to_string()))
.collect();
assert!(!text.contains('\x1b'), "{text:?}");
}
}