use std::collections::HashSet;
use crate::config::Config;
use crate::render::render_segments;
use crate::types::{Section, SectionKind};
use crate::utils::{truncate_with_ellipsis, visible_len};
pub fn build_statusline(sections: Vec<Section>, max_width: usize, config: &Config) -> Vec<String> {
if config.display.multiline {
build_statusline_multiline(sections, max_width, config)
} else {
vec![build_statusline_single(sections, max_width, config)]
}
}
fn calculate_total_width(sections: &[&Section], config: &Config) -> usize {
if sections.is_empty() {
return 0;
}
let content_width: usize = sections
.iter()
.map(|s| s.width + 2 * config.display.section_padding)
.sum();
let count = sections.len();
let separator_overhead = if config.display.use_powerline {
count
} else if count > 1 {
(count - 1) * visible_len(&config.display.segment_separator)
} else {
0
};
content_width + separator_overhead
}
fn deduplicate_sections(sections: &[Section]) -> Vec<&Section> {
let detailed_labels: HashSet<&str> = sections
.iter()
.filter_map(|s| match &s.kind {
SectionKind::QuotaDetailed(l) => Some(l.as_str()),
_ => None,
})
.collect();
let has_context_detailed = sections
.iter()
.any(|s| matches!(s.kind, SectionKind::ContextDetailed));
sections
.iter()
.filter(|section| match §ion.kind {
SectionKind::QuotaCompact(label) => !detailed_labels.contains(label.as_str()),
SectionKind::ContextCompact => !has_context_detailed,
_ => true,
})
.collect()
}
fn build_statusline_single(sections: Vec<Section>, max_width: usize, config: &Config) -> String {
if sections.is_empty() {
return String::new();
}
let mut sorted = sections;
sorted.sort_by_key(|s| s.priority);
while sorted.len() > 1 {
let deduped = deduplicate_sections(&sorted);
let total_width = calculate_total_width(&deduped, config);
if total_width <= max_width {
return render_segments(&deduped, config);
}
sorted.pop();
}
if let Some(last) = sorted.first() {
let rendered = render_segments(&[last], config);
let visible = visible_len(&rendered);
if visible > max_width {
return truncate_with_ellipsis(&rendered, max_width);
}
return rendered;
}
String::new()
}
fn build_statusline_multiline(
sections: Vec<Section>,
max_width: usize,
config: &Config,
) -> Vec<String> {
if sections.is_empty() {
return vec![];
}
let mut sorted = sections;
sorted.sort_by_key(|s| s.priority);
let deduped = deduplicate_sections(&sorted);
let mut lines: Vec<String> = Vec::new();
let mut current_line: Vec<&Section> = Vec::new();
for section in deduped {
let mut test_line = current_line.clone();
test_line.push(section);
let total_width = calculate_total_width(&test_line, config);
if total_width <= max_width || current_line.is_empty() {
current_line.push(section);
} else {
lines.push(render_segments(¤t_line, config));
current_line.clear();
current_line.push(section);
}
}
if !current_line.is_empty() {
lines.push(render_segments(¤t_line, config));
}
lines
}
#[cfg(test)]
mod tests {
use super::*;
use crate::colors::SectionColors;
const TEST_COLORS: SectionColors = SectionColors {
background: Some((0, 0, 0)),
foreground: (255, 255, 255),
details: (128, 128, 128),
};
#[test]
fn test_section_dedup() {
let sections = vec![
Section::new_quota_compact("5h", "5h: 10%".to_string(), 1, TEST_COLORS),
Section::new_quota_detailed("5h", "5h: 10% (1h)".to_string(), 101, TEST_COLORS),
];
let config = Config::default();
let result = build_statusline(sections.clone(), 100, &config);
let joined = result.join(" ");
assert!(joined.contains("(1h)"));
let result_narrow = build_statusline(sections, 5, &config);
let joined_narrow = result_narrow.join(" ");
assert!(joined_narrow.contains("5h"));
}
}