use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use unicode_width::UnicodeWidthStr;
use crate::tui::skin::Colors;
const BAR_FULL: &str = "\u{2588}";
const BAR_EMPTY: &str = "\u{2591}";
pub fn section_header(
title: &str,
width: usize,
colors: &Colors,
) -> Line<'static> {
let label = format!("{} ", title.to_uppercase());
let used = UnicodeWidthStr::width(label.as_str());
let rule = "\u{2500}".repeat(width.saturating_sub(used));
Line::from(vec![
Span::styled(
label,
Style::default()
.fg(colors.accent)
.add_modifier(Modifier::BOLD),
),
Span::styled(rule, Style::default().fg(colors.dim)),
])
}
pub fn field(
label: &str,
value: &str,
label_width: usize,
colors: &Colors,
) -> Line<'static> {
Line::from(vec![
Span::styled(
format!("{label:<label_width$}"),
Style::default().fg(colors.dim),
),
Span::styled(value.to_string(), Style::default().fg(colors.foreground)),
])
}
pub fn log_line(entry: &str, colors: &Colors) -> Line<'static> {
match entry.split_once(' ') {
Some((hash, subject)) => Line::from(vec![
Span::styled(format!("{hash} "), Style::default().fg(colors.dim)),
Span::styled(
subject.to_string(),
Style::default().fg(colors.foreground),
),
]),
None => Line::from(Span::styled(
entry.to_string(),
Style::default().fg(colors.foreground),
)),
}
}
pub fn bar(share: f32, width: usize) -> String {
if width == 0 {
return String::new();
}
let share = share.clamp(0.0, 1.0);
let mut filled = (share * width as f32).round() as usize;
if share > 0.0 {
filled = filled.max(1);
}
let filled = filled.min(width);
BAR_FULL.repeat(filled) + &BAR_EMPTY.repeat(width - filled)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
fn colors() -> Colors {
Colors::from_palette(&Config::default().palette())
}
#[test]
fn bar_fills_proportionally_and_never_overflows() {
assert_eq!(bar(0.0, 4), "░░░░");
assert_eq!(bar(0.5, 4), "██░░");
assert_eq!(bar(1.0, 4), "████");
assert_eq!(bar(1.5, 4), "████");
assert_eq!(bar(-1.0, 4), "░░░░");
assert_eq!(bar(0.5, 0), "");
}
#[test]
fn bar_shows_a_tiny_share_as_one_cell() {
assert_eq!(bar(0.01, 20).chars().filter(|c| *c == '█').count(), 1);
}
#[test]
fn field_pads_the_label_column() {
let line = field("Kind", "git", 10, &colors());
assert_eq!(line.spans[0].content.as_ref(), "Kind ");
assert_eq!(line.spans[1].content.as_ref(), "git");
}
#[test]
fn section_header_uppercases_and_rules_to_the_edge() {
let line = section_header("git", 20, &colors());
assert_eq!(line.spans[0].content.as_ref(), "GIT ");
assert_eq!(line.width(), 20);
}
#[test]
fn log_line_dims_the_hash_only() {
let line = log_line("a1b2c3d feat: add stats", &colors());
assert_eq!(line.spans.len(), 2);
assert_eq!(line.spans[0].content.as_ref(), "a1b2c3d ");
assert_eq!(log_line("nospace", &colors()).spans.len(), 1);
}
}