term_table/
row.rs

1use crate::table_cell::{string_width, Alignment, TableCell};
2use crate::{RowPosition, TableStyle};
3use std::cmp::max;
4use unicode_width::UnicodeWidthChar;
5
6/// A set of table cells
7#[derive(Debug, Clone)]
8pub struct Row {
9    pub cells: Vec<TableCell>,
10    /// Whether the row should have a top boarder or not
11    pub has_separator: bool,
12}
13
14impl Row {
15    pub fn new<I, T>(cells: I) -> Row
16    where
17        T: Into<TableCell>,
18        I: IntoIterator<Item = T>,
19    {
20        let mut row = Row {
21            cells: vec![],
22            has_separator: true,
23        };
24
25        for entry in cells.into_iter() {
26            row.cells.push(entry.into());
27        }
28
29        row
30    }
31
32    pub fn empty() -> Row {
33        Row {
34            cells: vec![],
35            has_separator: true,
36        }
37    }
38
39    pub fn without_separator<I, T>(cells: I) -> Row
40    where
41        T: Into<TableCell>,
42        I: IntoIterator<Item = T>,
43    {
44        let mut row = Self::new(cells);
45        row.has_separator = false;
46        row
47    }
48
49    /// Formats a row based on the provided table style
50    pub fn format(&self, column_widths: &[usize], style: &TableStyle) -> String {
51        let mut buf = String::new();
52
53        // Since a cell can span multiple columns we need to track
54        // how many columns we have actually spanned. We cannot just depend
55        // on the index of the current cell when iterating
56        let mut spanned_columns = 0;
57
58        // The height of the row determined by how many times a cell had to wrap
59        let mut row_height = 0;
60
61        // Wrapped cell content
62        let mut wrapped_cells = Vec::new();
63
64        // The first thing we do is wrap the cells if their
65        // content is greater than the max width of the column they are in
66        for cell in &self.cells {
67            let mut width = 0;
68            // Iterate from 0 to the cell's col_span and add up all the max width
69            // values for each column so we can properly pad the cell content later
70            for j in 0..cell.col_span {
71                width += column_widths[j + spanned_columns];
72            }
73            // Wrap to the total width - col_span to account for separators
74            let wrapped_cell = cell.wrapped_content(width + cell.col_span - 1);
75            row_height = max(row_height, wrapped_cell.len());
76            wrapped_cells.push(wrapped_cell);
77            spanned_columns += cell.col_span;
78        }
79
80        // reset spanned_columns so we can reuse it in the next loop
81        spanned_columns = 0;
82
83        // Row lines to combine into the final string at the end
84        let mut lines = vec![String::new(); row_height];
85
86        // We need to iterate over all of the column widths
87        // We may not have as many cells as column widths, or the cells may not even span
88        // as many columns as are in column widths. In that case weill will create empty cells
89        for col_idx in 0..column_widths.len() {
90            // Check to see if we actually have a cell for the column index
91            // Otherwise we will just need to print out empty space as filler
92            if self.cells.len() > col_idx {
93                // Number of characters spanned by column
94                let mut cell_span = 0;
95
96                // Get the cell using the column index
97                //
98                // This is a little bit confusing because cells and columns aren't always one to one
99                // We may have fewer cells than columns or some cells may span multiple columns
100                // If there are fewer cells than columns we just end drawing empty cells in the else block
101                // If there are fewer cells than columns but they span the total number of columns we just break out
102                // of the outer for loop at the end. We know how many cells we've spanned by adding the cell's col_span to spanned_columns
103                let cell = &self.cells[col_idx];
104                // Calculate the cell span by adding up the widths of the columns spanned by the cell
105                for c in 0..cell.col_span {
106                    cell_span += column_widths[spanned_columns + c];
107                }
108                // Since cells can wrap we need to loop over all of the lines
109                for (line_idx, line) in lines.iter_mut().enumerate().take(row_height) {
110                    // Check to see if the wrapped cell has a line for the line index
111                    if wrapped_cells[col_idx].len() > line_idx {
112                        // We may need to pad the cell if it's contents are not as wide as some other cell in the column
113                        let mut padding = 0;
114                        // We need to calculate the string_width because some characters take up extra space and we need to
115                        // ignore ANSI characters
116                        let str_width = string_width(&wrapped_cells[col_idx][line_idx]);
117                        if cell_span >= str_width {
118                            padding += cell_span - str_width;
119                            // If the cols_span is greater than one we need to add extra padding for the missing vertical characters
120                            if cell.col_span > 1 {
121                                padding += style.vertical.width().unwrap_or_default()
122                                    * (cell.col_span - 1); // Subtract one since we add a vertical character to the beginning
123                            }
124                        }
125
126                        // Finally we can push the string into the lines vec
127                        line.push_str(
128                            format!(
129                                "{}{}",
130                                style.vertical,
131                                self.pad_string(
132                                    padding,
133                                    cell.alignment,
134                                    &wrapped_cells[col_idx][line_idx]
135                                )
136                            )
137                            .as_str(),
138                        );
139                    } else {
140                        // If the cell doesn't have any content for this line just fill it with empty space
141                        line.push_str(
142                            format!(
143                                "{}{}",
144                                style.vertical,
145                                str::repeat(
146                                    " ",
147                                    column_widths[spanned_columns] * cell.col_span + cell.col_span
148                                        - 1
149                                )
150                            )
151                            .as_str(),
152                        );
153                    }
154                }
155                // Keep track of how many columns we have actually spanned since
156                // cells can be wider than a single column
157                spanned_columns += cell.col_span;
158            } else {
159                // If we don't have a cell for the coulumn then we just create an empty one
160                for line in lines.iter_mut().take(row_height) {
161                    line.push_str(
162                        format!(
163                            "{}{}",
164                            style.vertical,
165                            str::repeat(" ", column_widths[spanned_columns])
166                        )
167                        .as_str(),
168                    );
169                }
170                // Add one to the spanned column since the empty space is basically a cell
171                spanned_columns += 1;
172            }
173            // If we have spanned as many columns as there are then just break out of the loop
174            if spanned_columns == column_widths.len() {
175                break;
176            }
177        }
178        // Finally add all the lines together to create the row content
179        for line in &lines {
180            buf.push_str(line.clone().as_str());
181            buf.push(style.vertical);
182            buf.push('\n');
183        }
184        buf.pop();
185
186        buf
187    }
188
189    /// Generates the top separator for a row.
190    ///
191    /// The previous seperator is used to determine junction characters
192    pub fn gen_separator(
193        &self,
194        column_widths: &[usize],
195        style: &TableStyle,
196        row_position: RowPosition,
197        previous_separator: Option<String>,
198    ) -> String {
199        let mut buf = String::new();
200
201        // If the first cell has a col_span > 1 we need to set the next
202        // intersection point to that value
203        let mut next_intersection = match self.cells.first() {
204            Some(cell) => cell.col_span,
205            None => 1,
206        };
207
208        // Push the initial char for the row
209        buf.push(style.start_for_position(row_position));
210
211        let mut current_column = 0;
212
213        for (i, column_width) in column_widths.iter().enumerate() {
214            if i == next_intersection {
215                // Draw the intersection character for the start of the column
216                buf.push(style.intersect_for_position(row_position));
217
218                current_column += 1;
219
220                // If we still have remaining cells then we use the col_span to determine
221                // when the next intersection character should be drawn
222                if self.cells.len() > current_column {
223                    next_intersection += self.cells[current_column].col_span;
224                } else {
225                    // Otherwise we just draw an intersection for every column
226                    next_intersection += 1;
227                }
228            } else if i > 0 {
229                // This means the current cell has a col_span > 1
230                buf.push(style.horizontal);
231            }
232            // Fill in all of the horizontal space
233            buf.push_str(
234                str::repeat(style.horizontal.to_string().as_str(), *column_width).as_str(),
235            );
236        }
237
238        buf.push(style.end_for_position(row_position));
239
240        let mut out = String::new();
241
242        // Merge the previous seperator string with the current buffer
243        // This will handle cases where a cell above/below has a different col_span value
244        match previous_separator {
245            Some(prev) => {
246                for pair in buf.chars().zip(prev.chars()) {
247                    if pair.0 == style.outer_left_vertical || pair.0 == style.outer_right_vertical {
248                        // Always take the start and end characters of the current buffer
249                        out.push(pair.0);
250                    } else if pair.0 != style.horizontal || pair.1 != style.horizontal {
251                        out.push(style.merge_intersection_for_position(
252                            pair.1,
253                            pair.0,
254                            row_position,
255                        ));
256                    } else {
257                        out.push(style.horizontal);
258                    }
259                }
260                out
261            }
262            None => buf,
263        }
264    }
265
266    /// Returns a vector of split cell widths.
267    ///
268    /// A split width is the cell's total width divided by it's col_span value.
269    ///
270    /// Each cell's split width value is pushed into the resulting vector col_span times.
271    /// Returns a vec of tuples containing the cell width and the min cell width
272    pub fn split_column_widths(&self) -> Vec<(f32, usize)> {
273        let mut res = Vec::new();
274        for cell in &self.cells {
275            let val = cell.split_width();
276
277            let min = (cell.min_width() as f32 / cell.col_span as f32) as usize;
278
279            let add_one = cell.min_width() as f32 % cell.col_span as f32 > 0.001;
280            for i in 0..cell.col_span {
281                if add_one && i == cell.col_span - 1 {
282                    res.push((val + 1.0, min + 1));
283                } else {
284                    res.push((val, min));
285                }
286            }
287        }
288
289        res
290    }
291
292    /// Number of columns in the row.
293    ///
294    /// This is the sum of all cell's col_span values
295    pub fn num_columns(&self) -> usize {
296        self.cells.iter().map(|x| x.col_span).sum()
297    }
298
299    /// Pads a string accoding to the provided alignment
300    fn pad_string(&self, padding: usize, alignment: Alignment, text: &str) -> String {
301        match alignment {
302            Alignment::Left => return format!("{}{}", text, str::repeat(" ", padding)),
303            Alignment::Right => return format!("{}{}", str::repeat(" ", padding), text),
304            Alignment::Center => {
305                let half_padding = padding as f32 / 2.0;
306                return format!(
307                    "{}{}{}",
308                    str::repeat(" ", half_padding.ceil() as usize),
309                    text,
310                    str::repeat(" ", half_padding.floor() as usize)
311                );
312            }
313        }
314    }
315
316    /// Adds a cell to the row
317    pub fn add_cell(&mut self, cell: TableCell) {
318        self.cells.push(cell);
319    }
320
321}