1use std::sync::Arc;
12
13use crate::align::{Align, HorizontalAlign};
14use crate::console::{Console, ConsoleOptions};
15use crate::measure::Measurement;
16use crate::protocol::Renderable;
17use crate::segment::Segment;
18use crate::table::{Cell, Table};
19
20pub struct Columns {
22 items: Vec<Cell>,
23 padding: (usize, usize, usize, usize),
25 expand: bool,
26 equal: bool,
27 width: Option<usize>,
28 column_first: bool,
29 right_to_left: bool,
30 align: Option<HorizontalAlign>,
31 title: Option<String>,
32}
33
34impl Columns {
35 pub fn new(items: Vec<String>) -> Self {
39 Columns::from_cells(items.into_iter().map(Cell::Markup).collect())
40 }
41
42 pub fn from_cells(items: Vec<Cell>) -> Self {
45 Columns {
46 items,
47 padding: (0, 1, 0, 1),
48 expand: false,
49 equal: false,
50 width: None,
51 column_first: false,
52 right_to_left: false,
53 align: None,
54 title: None,
55 }
56 }
57
58 pub fn add_renderable(&mut self, item: impl Into<Cell>) -> &mut Self {
60 self.items.push(item.into());
61 self
62 }
63
64 pub fn padding(mut self, padding: (usize, usize, usize, usize)) -> Self {
68 self.padding = padding;
69 self
70 }
71
72 pub fn width(mut self, width: usize) -> Self {
75 self.width = Some(width);
76 self
77 }
78
79 pub fn column_first(mut self, column_first: bool) -> Self {
82 self.column_first = column_first;
83 self
84 }
85
86 pub fn right_to_left(mut self, right_to_left: bool) -> Self {
88 self.right_to_left = right_to_left;
89 self
90 }
91
92 pub fn align(mut self, align: HorizontalAlign) -> Self {
94 self.align = Some(align);
95 self
96 }
97
98 pub fn title(mut self, title: impl Into<String>) -> Self {
100 self.title = Some(title.into());
101 self
102 }
103
104 pub fn expand(mut self, expand: bool) -> Self {
106 self.expand = expand;
107 self
108 }
109
110 pub fn equal(mut self, equal: bool) -> Self {
112 self.equal = equal;
113 self
114 }
115}
116
117fn iter_order(item_count: usize, column_count: usize, column_first: bool) -> Vec<Option<usize>> {
120 let mut order: Vec<Option<usize>> = if column_first {
121 let mut column_lengths = vec![item_count / column_count; column_count];
122 for length in column_lengths.iter_mut().take(item_count % column_count) {
123 *length += 1;
124 }
125 let row_count = item_count.div_ceil(column_count);
126 let mut cells = vec![vec![None; column_count]; row_count];
127 let (mut row, mut col) = (0, 0);
128 for index in 0..item_count {
129 cells[row][col] = Some(index);
130 column_lengths[col] -= 1;
131 if column_lengths[col] > 0 {
132 row += 1;
133 } else {
134 col += 1;
135 row = 0;
136 }
137 }
138 cells
140 .into_iter()
141 .flatten()
142 .map_while(|index| index.map(Some))
143 .collect()
144 } else {
145 (0..item_count).map(Some).collect()
146 };
147 let remainder = item_count % column_count;
148 if remainder != 0 {
149 order.resize(order.len() + (column_count - remainder), None);
150 }
151 order
152}
153
154fn compute_column_count(
157 widths: &[usize],
158 max_width: usize,
159 width_padding: usize,
160 column_first: bool,
161) -> usize {
162 let mut column_count = widths.len();
163 while column_count > 1 {
164 let sequence = iter_order(widths.len(), column_count, column_first)
165 .into_iter()
166 .map(|index| index.map_or(0, |index| widths[index]));
167 let mut columns: Vec<usize> = Vec::new();
168 let mut column_no = 0usize;
169 let mut broke = false;
170 for width in sequence {
171 if column_no == columns.len() {
172 columns.push(width);
173 } else {
174 columns[column_no] = columns[column_no].max(width);
175 }
176 let total: usize =
177 columns.iter().sum::<usize>() + width_padding * columns.len().saturating_sub(1);
178 if total > max_width {
179 column_count = columns.len().saturating_sub(1);
180 broke = true;
181 break;
182 }
183 column_no = (column_no + 1) % column_count;
184 }
185 if !broke {
186 break;
187 }
188 }
189 column_count.max(1)
190}
191
192impl Renderable for Columns {
193 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
194 if self.items.is_empty() {
195 return Vec::new();
196 }
197 let (top, right, bottom, left) = self.padding;
198 let width_padding = left.max(right);
199 let renderables: Vec<Cell> = self
203 .items
204 .iter()
205 .map(|item| match item {
206 Cell::Markup(markup) => Cell::Text(console.render_str(markup, None)),
207 other => other.clone(),
208 })
209 .collect();
210
211 let mut widths: Vec<usize> = renderables
214 .iter()
215 .map(|cell| cell.measure_cell(console, options).maximum)
216 .collect();
217 if self.equal {
218 let widest = widths.iter().copied().max().unwrap_or(0);
219 widths = vec![widest; widths.len()];
220 }
221
222 let mut table = Table::grid()
223 .padding(top, right, bottom, left)
224 .collapse_padding(true)
225 .pad_edge(false)
226 .expand(self.expand);
227 if let Some(title) = &self.title {
228 table = table.title(title.clone());
229 }
230 let column_count = match self.width {
231 Some(width) => {
232 let Some(column_count) = options.max_width.checked_div(width + width_padding)
238 else {
239 return Vec::new();
240 };
241 let column_count = column_count.max(1);
242 for _ in 0..column_count {
243 table.add_column("").column_width(width);
244 }
245 column_count
246 }
247 None => {
248 let column_count = compute_column_count(
249 &widths,
250 options.max_width,
251 width_padding,
252 self.column_first,
253 );
254 for _ in 0..column_count {
255 table.add_column("");
256 }
257 column_count
258 }
259 };
260
261 let equal_width = widths.first().copied().unwrap_or(0);
268 let cells: Vec<Cell> = iter_order(renderables.len(), column_count, self.column_first)
269 .into_iter()
270 .map(|index| {
271 let Some(index) = index else {
273 return Cell::Markup(String::new());
274 };
275 let mut cell = renderables[index].clone();
276 if self.equal {
277 if let Cell::Renderable(renderable) = &cell {
278 cell = Cell::Renderable(Arc::new(ConstrainCell {
279 renderable: renderable.clone(),
280 width: equal_width,
281 }));
282 }
283 }
284 if let Some(align) = self.align {
285 let child: Arc<dyn Renderable + Send + Sync> = match cell {
286 Cell::Renderable(renderable) => renderable,
287 Cell::Text(text) => Arc::new(text),
288 Cell::Markup(markup) => Arc::new(console.render_str(&markup, None)),
289 };
290 cell = Cell::Renderable(Arc::new(AlignCell { child, align }));
291 }
292 cell
293 })
294 .collect();
295 for row in cells.chunks(column_count) {
296 let mut row = row.to_vec();
297 if self.right_to_left {
298 row.reverse();
299 }
300 table.add_row_cells(row);
301 }
302 table.rich_render(console, options)
303 }
304}
305
306struct AlignCell {
309 child: Arc<dyn Renderable + Send + Sync>,
310 align: HorizontalAlign,
311}
312
313impl Renderable for AlignCell {
314 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
315 Align::render_child(self.child.as_ref(), self.align, console, options)
316 }
317
318 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
319 Measurement::get(console, options, self.child.as_ref())
320 }
321}
322
323struct ConstrainCell {
326 renderable: Arc<dyn Renderable + Send + Sync>,
327 width: usize,
328}
329
330impl Renderable for ConstrainCell {
331 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
332 let options = options.update_width(self.width.min(options.max_width));
333 if options.max_width < 1 {
334 return Vec::new();
335 }
336 self.renderable.rich_render(console, &options)
337 }
338
339 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
340 Measurement::get(
341 console,
342 &options.update_width(self.width),
343 self.renderable.as_ref(),
344 )
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::color::ColorSystem;
352
353 fn console(width: usize) -> Console {
354 Console::builder()
355 .force_terminal(true)
356 .color_system(Some(ColorSystem::Truecolor))
357 .width(width)
358 .build()
359 }
360
361 fn columns(items: &[&str]) -> Columns {
362 Columns::new(items.iter().map(|s| s.to_string()).collect())
363 }
364
365 #[test]
366 fn packs_into_two_rows() {
367 let out =
368 console(20).render_export(&columns(&["one", "two", "three", "four", "five", "six"]));
369 assert_eq!(out, "one two three four\nfive six \n");
370 }
371
372 #[test]
373 fn single_row_when_it_fits() {
374 let out = console(30).render_export(&columns(&["alpha", "beta", "gamma", "delta"]));
375 assert_eq!(out, "alpha beta gamma delta\n");
376 }
377
378 #[test]
379 fn truncates_an_item_wider_than_the_width() {
380 let out = console(8).render_export(&columns(&["supercalifragilistic"]));
381 assert_eq!(out, "superca…\n");
382 }
383
384 #[test]
385 fn wraps_an_item_wider_than_the_width() {
386 let out = console(13).render_export(&columns(&["name name name"]));
387 assert_eq!(out, "name name \nname \n");
388 }
389}