use std::num::NonZeroU16;
use ctl_text::{Context, Line, Measurement, Overflow, Renderable, Text};
use crate::lines::{indent_lines, literal};
const LABEL_INDENT: u16 = 2;
const DESCRIPTION_INDENT: u16 = 4;
#[derive(Clone, Debug, Default)]
pub struct Definitions {
entries: Vec<Entry>,
}
#[derive(Clone, Debug)]
struct Entry {
labels: Vec<Text>,
description: Vec<Text>,
}
impl Definitions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn entry<L, D>(
self,
labels: impl IntoIterator<Item = L>,
description: impl IntoIterator<Item = D>,
) -> Self
where
L: Into<Text>,
D: Into<Text>,
{
self.push(
labels.into_iter().map(Into::into).collect(),
description.into_iter().map(Into::into).collect(),
)
}
fn push(mut self, labels: Vec<Text>, description: Vec<Text>) -> Self {
self.entries.push(Entry {
labels,
description,
});
self
}
fn lines(&self, context: &Context) -> Vec<Line> {
let (width, tab_width) = (context.width(), context.tab_width());
let mut lines = Vec::new();
for entry in &self.entries {
let labels = join_cells(&entry.labels, tab_width);
let description = join_cells(&entry.description, tab_width);
for (part, indent) in [(labels, LABEL_INDENT), (description, DESCRIPTION_INDENT)] {
if !part.is_empty() {
let fitted = part.fit(width.saturating_sub(indent), Overflow::KeepWords);
lines.extend(indent_lines(fitted, indent));
}
}
}
lines
}
}
fn join_cells(cells: &[Text], tab_width: NonZeroU16) -> Line {
let mut joined = Line::new();
for line in cells
.iter()
.flat_map(|cell| cell.lines(tab_width))
.filter(|line| !line.is_empty())
{
if !joined.is_empty() {
joined.push(literal(" "));
}
joined.extend(line.spans().iter().cloned());
}
joined
}
impl Renderable for Definitions {
fn measure(&self, context: &Context) -> Measurement {
let widest = |context: &Context| {
self.lines(context)
.iter()
.map(Line::width)
.max()
.unwrap_or(0)
};
let unbounded = Context::unbounded().at_tab_width(context.tab_width());
Measurement::new(widest(&context.at_width(1)), widest(&unbounded))
}
fn render(&self, context: &Context) -> Vec<Line> {
self.lines(context)
}
}