use std::borrow::Cow;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LayoutDecision {
#[non_exhaustive]
PriorityDrop {
id: Cow<'static, str>,
priority: u8,
terminal_width: u16,
overflow: u32,
dropped_width: u16,
},
#[non_exhaustive]
ShrinkApplied {
id: Cow<'static, str>,
from: u16,
to: u16,
target: u16,
},
#[non_exhaustive]
ReflowApplied {
id: Cow<'static, str>,
from: u16,
to: u16,
target: u16,
},
#[non_exhaustive]
WidthBoundUnderMinDrop {
id: Cow<'static, str>,
rendered_width: u16,
min: u16,
},
#[non_exhaustive]
WidthBoundOverMaxTruncate {
id: Cow<'static, str>,
rendered_width: u16,
max: u16,
},
}
impl LayoutDecision {
#[must_use]
pub fn remediation(&self) -> Option<&'static str> {
match self {
Self::ShrinkApplied { .. } => Some("Set `width.max` to clamp earlier"),
Self::WidthBoundOverMaxTruncate { .. } => {
Some("Increase `width.max` or lower `priority`")
}
Self::PriorityDrop { .. } => None,
Self::ReflowApplied { .. } => None,
Self::WidthBoundUnderMinDrop { .. } => None,
}
}
#[must_use]
pub(crate) fn priority_drop(
id: Cow<'static, str>,
priority: u8,
terminal_width: u16,
overflow: u32,
dropped_width: u16,
) -> Self {
debug_assert!(
priority > 0 && overflow >= 1,
"PriorityDrop invariants: priority>0, overflow>=1 (got priority={priority}, overflow={overflow})"
);
Self::PriorityDrop {
id,
priority,
terminal_width,
overflow,
dropped_width,
}
}
#[must_use]
pub(crate) fn shrink_applied(id: Cow<'static, str>, from: u16, to: u16, target: u16) -> Self {
debug_assert!(
to <= target && target < from,
"ShrinkApplied requires to <= target < from (got from={from}, to={to}, target={target})"
);
Self::ShrinkApplied {
id,
from,
to,
target,
}
}
#[must_use]
pub(crate) fn reflow_applied(id: Cow<'static, str>, from: u16, to: u16, target: u16) -> Self {
debug_assert!(
to <= target && target < from,
"ReflowApplied requires to <= target < from (got from={from}, to={to}, target={target})"
);
Self::ReflowApplied {
id,
from,
to,
target,
}
}
#[must_use]
pub(crate) fn width_bound_under_min_drop(
id: Cow<'static, str>,
rendered_width: u16,
min: u16,
) -> Self {
debug_assert!(
rendered_width < min,
"WidthBoundUnderMinDrop requires rendered_width < min (got rendered_width={rendered_width}, min={min})"
);
Self::WidthBoundUnderMinDrop {
id,
rendered_width,
min,
}
}
#[must_use]
pub(crate) fn width_bound_over_max_truncate(
id: Cow<'static, str>,
rendered_width: u16,
max: u16,
) -> Self {
debug_assert!(
rendered_width > max,
"WidthBoundOverMaxTruncate requires rendered_width > max (got rendered_width={rendered_width}, max={max})"
);
Self::WidthBoundOverMaxTruncate {
id,
rendered_width,
max,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn remediation_pins_per_variant_phrasing() {
let shrink = LayoutDecision::shrink_applied(Cow::Borrowed("git"), 20, 17, 17);
let over_max = LayoutDecision::width_bound_over_max_truncate(Cow::Borrowed("git"), 50, 30);
let drop = LayoutDecision::priority_drop(Cow::Borrowed("git"), 200, 80, 12, 6);
let reflow = LayoutDecision::reflow_applied(Cow::Borrowed("git"), 20, 17, 17);
let under_min = LayoutDecision::width_bound_under_min_drop(Cow::Borrowed("git"), 3, 5);
assert_eq!(
shrink.remediation(),
Some("Set `width.max` to clamp earlier")
);
assert_eq!(
over_max.remediation(),
Some("Increase `width.max` or lower `priority`")
);
assert!(drop.remediation().is_none());
assert!(reflow.remediation().is_none());
assert!(under_min.remediation().is_none());
}
#[test]
fn constructors_accept_invariant_boundaries() {
let _ = LayoutDecision::shrink_applied(Cow::Borrowed("a"), 20, 17, 17);
let _ = LayoutDecision::reflow_applied(Cow::Borrowed("a"), 20, 17, 17);
let _ = LayoutDecision::shrink_applied(Cow::Borrowed("a"), 20, 16, 17);
let _ = LayoutDecision::reflow_applied(Cow::Borrowed("a"), 20, 16, 17);
let _ = LayoutDecision::priority_drop(Cow::Borrowed("a"), 1, 80, 1, 1);
let _ = LayoutDecision::width_bound_under_min_drop(Cow::Borrowed("a"), 4, 5);
let _ = LayoutDecision::width_bound_over_max_truncate(Cow::Borrowed("a"), 6, 5);
}
const _ASSERT_EQ_DERIVED: fn() = || {
fn assert_eq<T: Eq>() {}
assert_eq::<LayoutDecision>();
};
#[test]
fn derives_clone_debug_partial_eq() {
let d = LayoutDecision::shrink_applied(Cow::Borrowed("git"), 20, 17, 17);
let cloned = d.clone();
assert_eq!(d, cloned);
let dbg = format!("{d:?}");
assert!(dbg.contains("ShrinkApplied"), "got {dbg:?}");
assert!(dbg.contains("id: \"git\""), "got {dbg:?}");
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "PriorityDrop invariants: priority>0, overflow>=1")]
fn priority_drop_panics_in_debug_when_priority_is_zero() {
let _ = LayoutDecision::priority_drop(Cow::Borrowed("git"), 0, 80, 12, 6);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "PriorityDrop invariants: priority>0, overflow>=1")]
fn priority_drop_panics_when_overflow_is_zero() {
let _ = LayoutDecision::priority_drop(Cow::Borrowed("git"), 200, 80, 0, 6);
}
#[test]
fn priority_drop_accepts_zero_dropped_width() {
let _ = LayoutDecision::priority_drop(Cow::Borrowed("empty"), 200, 80, 5, 0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ShrinkApplied requires to <= target < from")]
fn shrink_applied_panics_when_target_not_below_from() {
let _ = LayoutDecision::shrink_applied(Cow::Borrowed("git"), 20, 17, 20);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ShrinkApplied requires to <= target < from")]
fn shrink_applied_panics_when_to_above_target() {
let _ = LayoutDecision::shrink_applied(Cow::Borrowed("git"), 20, 18, 17);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ReflowApplied requires to <= target < from")]
fn reflow_applied_panics_when_target_not_below_from() {
let _ = LayoutDecision::reflow_applied(Cow::Borrowed("git"), 20, 17, 20);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ReflowApplied requires to <= target < from")]
fn reflow_applied_panics_when_to_above_target() {
let _ = LayoutDecision::reflow_applied(Cow::Borrowed("git"), 20, 18, 17);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ShrinkApplied requires to <= target < from")]
fn shrink_applied_panics_when_target_above_from() {
let _ = LayoutDecision::shrink_applied(Cow::Borrowed("git"), 20, 17, 21);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ReflowApplied requires to <= target < from")]
fn reflow_applied_panics_when_target_above_from() {
let _ = LayoutDecision::reflow_applied(Cow::Borrowed("git"), 20, 17, 21);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "WidthBoundUnderMinDrop requires rendered_width < min")]
fn under_min_drop_panics_when_rendered_at_or_above_min() {
let _ = LayoutDecision::width_bound_under_min_drop(Cow::Borrowed("git"), 5, 5);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "WidthBoundOverMaxTruncate requires rendered_width > max")]
fn over_max_truncate_panics_when_rendered_at_or_below_max() {
let _ = LayoutDecision::width_bound_over_max_truncate(Cow::Borrowed("git"), 30, 30);
}
}