use chrono::{DateTime, Utc};
use ratatui::{
Frame,
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Padding, Paragraph},
};
use crate::theme::Palette;
pub fn humanize_delta(dt: &DateTime<Utc>) -> String {
#[allow(clippy::cast_sign_loss)]
let secs = (Utc::now() - *dt).num_seconds().max(0) as u64;
if secs < 60 {
format!("{secs}s ago")
} else if secs < 3600 {
format!("{}m ago", secs / 60)
} else if secs < 86400 {
format!("{}h ago", secs / 3600)
} else {
format!("{}d ago", secs / 86400)
}
}
pub fn truncate(s: &str, max_chars: usize) -> String {
if max_chars == 0 {
return String::new();
}
let char_count = s.chars().count();
if char_count <= max_chars {
return s.to_owned();
}
let mut out: String = s.chars().take(max_chars.saturating_sub(1)).collect();
out.push('\u{2026}'); out
}
pub(crate) fn section_header(label: &str, p: &Palette) -> Line<'static> {
let rule = "\u{2501}".repeat(3); Line::from(vec![
Span::styled(format!("{rule} "), Style::default().fg(p.accent)),
Span::styled(label.to_owned(), Style::default().fg(p.accent).add_modifier(Modifier::BOLD)),
])
}
pub(crate) fn render_detail_header(
f: &mut Frame,
lines: Vec<Line<'static>>,
area: Rect,
p: &Palette,
) {
if area.height == 0 {
return;
}
let rule_row = area.height.saturating_sub(1);
let content_h = area.height.saturating_sub(1);
let content_area = Rect { x: area.x, y: area.y, width: area.width, height: content_h };
let rule_area = Rect { x: area.x, y: area.y + rule_row, width: area.width, height: 1 };
let block = Block::default()
.style(Style::default().bg(p.help_bg).fg(p.foreground))
.padding(Padding::new(2, 2, 1, 0));
let paragraph = Paragraph::new(lines).block(block);
f.render_widget(paragraph, content_area);
let rule_text = "\u{2501}".repeat(usize::from(rule_area.width));
let rule = Paragraph::new(Line::from(Span::styled(
rule_text,
Style::default().fg(p.accent).bg(p.background),
)));
f.render_widget(rule, rule_area);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_short_unchanged() {
assert_eq!(truncate("hello", 20), "hello");
}
#[test]
fn truncate_at_limit_unchanged() {
assert_eq!(truncate("hello", 5), "hello");
}
#[test]
fn truncate_appends_ellipsis() {
let t = truncate("hello world", 8);
assert!(t.chars().count() <= 8);
assert!(t.ends_with('\u{2026}'));
}
#[test]
fn truncate_zero_max_returns_empty() {
assert_eq!(truncate("anything", 0), "");
}
}