use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) enum Priority {
Drop(u8),
Pinned,
}
pub(crate) struct Item<T> {
pub(crate) content: T,
pub(crate) priority: Priority,
}
pub(crate) struct Line<T> {
pub(crate) items: Vec<T>,
pub(crate) truncated: bool,
}
impl<T: fmt::Display> Line<T> {
pub(crate) fn render(&self, separator: &str, ellipsis: &str) -> String {
let joined = self
.items
.iter()
.map(T::to_string)
.collect::<Vec<_>>()
.join(separator);
if self.truncated {
format!("{joined}{ellipsis}")
} else {
joined
}
}
}
fn width(s: &str) -> usize {
s.chars().count()
}
pub(crate) fn budget<T: Clone + fmt::Display>(
items: &[Item<T>],
width_budget: usize,
separator: &str,
ellipsis: &str,
) -> Line<T> {
let mut current: Vec<&Item<T>> = items.iter().collect();
loop {
let dropped = current.len() < items.len();
let joined = current
.iter()
.map(|item| item.content.to_string())
.collect::<Vec<_>>()
.join(separator);
let rendered_len = if dropped {
width(&joined) + width(ellipsis)
} else {
width(&joined)
};
if rendered_len <= width_budget {
return Line {
items: current.iter().map(|item| item.content.clone()).collect(),
truncated: dropped,
};
}
let lowest_droppable = current
.iter()
.filter(|item| item.priority != Priority::Pinned)
.map(|item| item.priority)
.min();
match lowest_droppable {
Some(priority) => current.retain(|item| item.priority != priority),
None => {
return if width(&joined) <= width_budget {
Line {
items: current.iter().map(|item| item.content.clone()).collect(),
truncated: false,
}
} else {
Line {
items: Vec::new(),
truncated: false,
}
};
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn item(text: &str, priority: Priority) -> Item<String> {
Item {
content: text.to_string(),
priority,
}
}
#[test]
fn a_multi_byte_single_column_separator_is_counted_by_chars_not_bytes() {
let items = [item("a", Priority::Pinned), item("b", Priority::Pinned)];
let line = budget(&items, 5, " \u{b7} ", " ...");
assert_eq!(line.render(" \u{b7} ", " ..."), "a \u{b7} b");
}
#[test]
fn budget_width_checks_the_first_item_not_only_later_ones() {
let items = [
item("XXXXXXXXXX", Priority::Drop(1)),
item("Y", Priority::Pinned),
];
let line = budget(&items, 5, " ", " ...");
let rendered = line.render(" ", " ...");
assert_eq!(rendered, "Y ...");
assert!(rendered.len() <= 5, "must never overrun the given width");
}
#[test]
fn budget_reserves_the_ellipsis_inside_the_budget_rather_than_appending_it_after_a_fit_check() {
let items = [
item("AAAA", Priority::Drop(1)),
item("BB", Priority::Drop(2)),
item("C", Priority::Pinned),
];
let line = budget(&items, 8, " ", " ...");
assert_eq!(line.render(" ", " ..."), "C ...");
}
#[test]
fn budget_drops_the_ellipsis_from_the_last_surviving_item_rather_than_dropping_that_item() {
let items = [
item("AAAA", Priority::Drop(1)),
item("BB", Priority::Pinned),
];
let line = budget(&items, 5, " ", " ...");
assert_eq!(line.render(" ", " ..."), "BB");
}
#[test]
fn budget_renders_nothing_once_even_the_pinned_item_alone_cannot_fit() {
let items = [item("BB", Priority::Pinned)];
let line = budget(&items, 1, " ", " ...");
assert_eq!(line.render(" ", " ..."), "");
}
#[test]
fn budget_drops_a_shared_priority_group_together_never_one_item_alone() {
let items = [
item("LAUNCHER", Priority::Drop(1)),
item("ACTION", Priority::Drop(1)),
item("HELP", Priority::Pinned),
];
let line = budget(&items, 16, " ", " ...");
assert_eq!(line.render(" ", " ..."), "HELP ...");
}
#[test]
fn degrade_never_reintroduces_the_first_item_exemption_guard() {
let banned = [
format!("{} {} 0", "i", ">"),
format!("{} {} 0", "index", ">"),
format!("{}(1)", ".skip"),
];
let source = crate::test_support::production_source(include_str!("degrade.rs"));
let offending: Vec<&str> = source
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.filter(|line| banned.iter().any(|needle| line.contains(needle.as_str())))
.collect();
assert!(
offending.is_empty(),
"found a first-item exemption guard: {offending:?}"
);
}
}