1use std::sync::Arc;
13
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}
28
29impl Columns {
30 pub fn new(items: Vec<String>) -> Self {
34 Columns::from_cells(items.into_iter().map(Cell::Markup).collect())
35 }
36
37 pub fn from_cells(items: Vec<Cell>) -> Self {
40 Columns {
41 items,
42 padding: (0, 1, 0, 1),
43 expand: false,
44 equal: false,
45 }
46 }
47
48 pub fn expand(mut self, expand: bool) -> Self {
50 self.expand = expand;
51 self
52 }
53
54 pub fn equal(mut self, equal: bool) -> Self {
56 self.equal = equal;
57 self
58 }
59}
60
61fn iter_widths(widths: &[usize], column_count: usize) -> Vec<usize> {
65 let mut sequence = widths.to_vec();
66 let remainder = widths.len() % column_count;
67 if remainder != 0 {
68 sequence.resize(widths.len() + (column_count - remainder), 0);
69 }
70 sequence
71}
72
73fn compute_column_count(widths: &[usize], max_width: usize, width_padding: usize) -> usize {
76 let mut column_count = widths.len();
77 while column_count > 1 {
78 let sequence = iter_widths(widths, column_count);
79 let mut columns: Vec<usize> = Vec::new();
80 let mut column_no = 0usize;
81 let mut broke = false;
82 for width in sequence {
83 if column_no == columns.len() {
84 columns.push(width);
85 } else {
86 columns[column_no] = columns[column_no].max(width);
87 }
88 let total: usize =
89 columns.iter().sum::<usize>() + width_padding * columns.len().saturating_sub(1);
90 if total > max_width {
91 column_count = columns.len().saturating_sub(1);
92 broke = true;
93 break;
94 }
95 column_no = (column_no + 1) % column_count;
96 }
97 if !broke {
98 break;
99 }
100 }
101 column_count.max(1)
102}
103
104impl Renderable for Columns {
105 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
106 if self.items.is_empty() {
107 return Vec::new();
108 }
109 let (top, right, bottom, left) = self.padding;
110 let width_padding = left.max(right);
111 let renderables: Vec<Cell> = self
115 .items
116 .iter()
117 .map(|item| match item {
118 Cell::Markup(markup) => Cell::Text(console.render_str(markup, None)),
119 other => other.clone(),
120 })
121 .collect();
122
123 let mut widths: Vec<usize> = renderables
126 .iter()
127 .map(|cell| cell.measure_cell(console, options).maximum)
128 .collect();
129 if self.equal {
130 let widest = widths.iter().copied().max().unwrap_or(0);
131 widths = vec![widest; widths.len()];
132 }
133
134 let column_count = compute_column_count(&widths, options.max_width, width_padding);
135
136 let mut table = Table::grid()
143 .padding(top, right, bottom, left)
144 .collapse_padding(true)
145 .pad_edge(false)
146 .expand(self.expand);
147 for _ in 0..column_count {
148 table.add_column("");
149 }
150 let mut cells = renderables;
151 if self.equal {
152 let width = widths.first().copied().unwrap_or(0);
153 for cell in &mut cells {
154 if let Cell::Renderable(renderable) = cell {
155 *cell = Cell::Renderable(Arc::new(ConstrainCell {
156 renderable: renderable.clone(),
157 width,
158 }));
159 }
160 }
161 }
162 let remainder = cells.len() % column_count;
163 if remainder != 0 {
164 cells.resize(
167 cells.len() + (column_count - remainder),
168 Cell::Markup(String::new()),
169 );
170 }
171 for row in cells.chunks(column_count) {
172 table.add_row_cells(row.to_vec());
173 }
174 table.rich_render(console, options)
175 }
176}
177
178struct ConstrainCell {
181 renderable: Arc<dyn Renderable + Send + Sync>,
182 width: usize,
183}
184
185impl Renderable for ConstrainCell {
186 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
187 let options = options.update_width(self.width.min(options.max_width));
188 if options.max_width < 1 {
189 return Vec::new();
190 }
191 self.renderable.rich_render(console, &options)
192 }
193
194 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
195 Measurement::get(
196 console,
197 &options.update_width(self.width),
198 self.renderable.as_ref(),
199 )
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::color::ColorSystem;
207
208 fn console(width: usize) -> Console {
209 Console::builder()
210 .force_terminal(true)
211 .color_system(Some(ColorSystem::Truecolor))
212 .width(width)
213 .build()
214 }
215
216 fn columns(items: &[&str]) -> Columns {
217 Columns::new(items.iter().map(|s| s.to_string()).collect())
218 }
219
220 #[test]
221 fn packs_into_two_rows() {
222 let out =
223 console(20).render_export(&columns(&["one", "two", "three", "four", "five", "six"]));
224 assert_eq!(out, "one two three four\nfive six \n");
225 }
226
227 #[test]
228 fn single_row_when_it_fits() {
229 let out = console(30).render_export(&columns(&["alpha", "beta", "gamma", "delta"]));
230 assert_eq!(out, "alpha beta gamma delta\n");
231 }
232
233 #[test]
234 fn truncates_an_item_wider_than_the_width() {
235 let out = console(8).render_export(&columns(&["supercalifragilistic"]));
236 assert_eq!(out, "superca…\n");
237 }
238
239 #[test]
240 fn wraps_an_item_wider_than_the_width() {
241 let out = console(13).render_export(&columns(&["name name name"]));
242 assert_eq!(out, "name name \nname \n");
243 }
244}