1use crate::cells::cell_len;
12use crate::console::{Console, ConsoleOptions};
13use crate::protocol::Renderable;
14use crate::segment::Segment;
15use crate::style::Style;
16use crate::text::Text;
17
18pub struct Columns {
20 items: Vec<String>,
21 padding: (usize, usize, usize, usize),
23}
24
25impl Columns {
26 pub fn new(items: Vec<String>) -> Self {
28 Columns {
29 items,
30 padding: (0, 1, 0, 1),
31 }
32 }
33}
34
35fn iter_widths(widths: &[usize], column_count: usize) -> Vec<usize> {
39 let mut sequence = widths.to_vec();
40 let remainder = widths.len() % column_count;
41 if remainder != 0 {
42 sequence.resize(widths.len() + (column_count - remainder), 0);
43 }
44 sequence
45}
46
47fn compute_column_count(widths: &[usize], max_width: usize, width_padding: usize) -> usize {
50 let mut column_count = widths.len();
51 while column_count > 1 {
52 let sequence = iter_widths(widths, column_count);
53 let mut columns: Vec<usize> = Vec::new();
54 let mut column_no = 0usize;
55 let mut broke = false;
56 for width in sequence {
57 if column_no == columns.len() {
58 columns.push(width);
59 } else {
60 columns[column_no] = columns[column_no].max(width);
61 }
62 let total: usize =
63 columns.iter().sum::<usize>() + width_padding * columns.len().saturating_sub(1);
64 if total > max_width {
65 column_count = columns.len().saturating_sub(1);
66 broke = true;
67 break;
68 }
69 column_no = (column_no + 1) % column_count;
70 }
71 if !broke {
72 break;
73 }
74 }
75 column_count.max(1)
76}
77
78fn column_widths(widths: &[usize], column_count: usize) -> Vec<usize> {
80 let mut result = vec![0usize; column_count];
81 for (index, &width) in widths.iter().enumerate() {
82 let column = index % column_count;
83 result[column] = result[column].max(width);
84 }
85 result
86}
87
88impl Renderable for Columns {
89 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
90 if self.items.is_empty() {
91 return Vec::new();
92 }
93 let (_, right, _, left) = self.padding;
94 let width_padding = left.max(right);
95 let widths: Vec<usize> = self.items.iter().map(|item| cell_len(item)).collect();
96
97 let column_count = compute_column_count(&widths, options.max_width, width_padding);
98 let col_widths = column_widths(&widths, column_count);
99
100 let style = Some(Style::new());
101 let gap = " ".repeat(width_padding);
102
103 let mut lines: Vec<Vec<Segment>> = Vec::new();
104 let mut start = 0;
105 while start < self.items.len() {
106 let mut row: Vec<Segment> = Vec::new();
107 for (column, &col_width) in col_widths.iter().enumerate() {
108 if column > 0 {
109 row.push(Segment::new(gap.clone(), style.clone()));
110 }
111 let index = start + column;
112 let content = self.items.get(index).map(String::as_str).unwrap_or("");
113 let rendered = Text::new(content).render_lines(
114 console.theme(),
115 &Style::new(),
116 Some(col_width),
117 );
118 let cell = rendered.into_iter().next().unwrap_or_default();
119 let padded = Segment::adjust_line_length(&cell, col_width, style.clone());
120 row.extend(Segment::simplify(&padded));
121 }
122 lines.push(row);
123 start += column_count;
124 }
125
126 let mut segments = Vec::new();
127 let last = lines.len().saturating_sub(1);
128 for (index, line) in lines.into_iter().enumerate() {
129 segments.extend(line);
130 if index != last {
131 segments.push(Segment::line());
132 }
133 }
134 segments
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::color::ColorSystem;
142
143 fn console(width: usize) -> Console {
144 Console::builder()
145 .force_terminal(true)
146 .color_system(Some(ColorSystem::Truecolor))
147 .width(width)
148 .build()
149 }
150
151 fn columns(items: &[&str]) -> Columns {
152 Columns::new(items.iter().map(|s| s.to_string()).collect())
153 }
154
155 #[test]
156 fn packs_into_two_rows() {
157 let out =
158 console(20).render_export(&columns(&["one", "two", "three", "four", "five", "six"]));
159 assert_eq!(out, "one two three four\nfive six \n");
160 }
161
162 #[test]
163 fn single_row_when_it_fits() {
164 let out = console(30).render_export(&columns(&["alpha", "beta", "gamma", "delta"]));
165 assert_eq!(out, "alpha beta gamma delta\n");
166 }
167}