tabled/settings/width/
min_width.rs

1//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`].
2//!
3//! [`Table`]: crate::Table
4
5use std::iter::repeat_n;
6
7use papergrid::dimension::Estimate;
8
9use crate::{
10    grid::{
11        config::{ColoredConfig, Entity, Position},
12        dimension::CompleteDimension,
13        records::{
14            vec_records::Cell, ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut,
15        },
16        util::string::{get_line_width, get_lines},
17    },
18    settings::{
19        measurement::Measurement,
20        peaker::{Peaker, PriorityNone},
21        CellOption, TableOption, Width,
22    },
23};
24
25use super::util::get_table_total_width;
26
27/// [`MinWidth`] changes a content in case if it's length is lower then the boundary.
28///
29/// It can be applied to a whole table.
30///
31/// It does nothing in case if the content's length is bigger then the boundary.
32///
33/// Be aware that further changes of the table may cause the width being not set.
34/// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect.
35/// (You should use [`Padding`] first).
36///
37/// Be aware that it doesn't consider padding.
38/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
39///
40/// ## Examples
41///
42/// Cell change
43///
44/// ```
45/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}};
46///
47/// let data = ["Hello", "World", "!"];
48///
49/// let table = Table::new(&data)
50///     .with(Style::markdown())
51///     .with(Modify::new(Segment::all()).with(Width::increase(10)));
52/// ```
53/// Table change
54///
55/// ```
56/// use tabled::{Table, settings::Width};
57///
58/// let table = Table::new(&["Hello World!"]).with(Width::increase(5));
59/// ```
60///
61/// [`Padding`]: crate::settings::Padding
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
63pub struct MinWidth<W = usize, P = PriorityNone> {
64    width: W,
65    fill: char,
66    priority: P,
67}
68
69impl<W> MinWidth<W>
70where
71    W: Measurement<Width>,
72{
73    /// Creates a new instance of [`MinWidth`].
74    pub const fn new(width: W) -> Self {
75        Self {
76            width,
77            fill: ' ',
78            priority: PriorityNone::new(),
79        }
80    }
81}
82
83impl<W, P> MinWidth<W, P> {
84    /// Set's a fill character which will be used to fill the space
85    /// when increasing the length of the string to the set boundary.
86    ///
87    /// Used only if changing cells.
88    pub fn fill_with(mut self, c: char) -> Self {
89        self.fill = c;
90        self
91    }
92
93    /// Priority defines the logic by which a increase of width will be applied when is done for the whole table.
94    ///
95    /// - [`PriorityNone`] which inc the columns one after another.
96    /// - [`PriorityMax`] inc the biggest columns first.
97    /// - [`PriorityMin`] inc the lowest columns first.
98    ///
99    /// [`PriorityMax`]: crate::settings::peaker::PriorityMax
100    /// [`PriorityMin`]: crate::settings::peaker::PriorityMin
101    pub fn priority<PP: Peaker>(self, peacker: PP) -> MinWidth<W, PP> {
102        MinWidth {
103            fill: self.fill,
104            width: self.width,
105            priority: peacker,
106        }
107    }
108}
109
110impl<W, R, P> CellOption<R, ColoredConfig> for MinWidth<W, P>
111where
112    W: Measurement<Width>,
113    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
114    for<'a> &'a R: Records,
115    for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef<str>,
116{
117    fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
118        let width = self.width.measure(&*records, cfg);
119
120        let count_rows = records.count_rows();
121        let count_columns = records.count_columns();
122        let max_pos = Position::new(count_rows, count_columns);
123
124        for pos in entity.iter(count_rows, count_columns) {
125            if !max_pos.has_coverage(pos) {
126                continue;
127            }
128
129            let cell_width = records.get_width(pos);
130            if cell_width >= width {
131                continue;
132            }
133
134            let cell = records.get_text(pos);
135            let content = increase_width(cell, width, self.fill);
136            records.set(pos, content);
137        }
138    }
139}
140
141impl<W, P, R> TableOption<R, ColoredConfig, CompleteDimension> for MinWidth<W, P>
142where
143    W: Measurement<Width>,
144    P: Peaker,
145    R: Records + ExactRecords + PeekableRecords,
146    for<'a> &'a R: Records,
147    for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: Cell + AsRef<str>,
148{
149    fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimension) {
150        if records.count_rows() == 0 || records.count_columns() == 0 {
151            return;
152        }
153
154        let minwidth = self.width.measure(&*records, cfg);
155
156        dims.estimate(&*records, cfg);
157        let widths = dims.get_widths().expect("must be present");
158
159        let total_width = get_table_total_width(widths, cfg);
160        if total_width >= minwidth {
161            return;
162        }
163
164        let widths = get_increase_list(widths, minwidth, total_width, self.priority);
165        dims.set_widths(widths);
166    }
167
168    fn hint_change(&self) -> Option<Entity> {
169        // NOTE:
170        // We set proper widths,
171        // While keeping height unchanged,
172        // So we can safely assume nothing needs reestimation.
173        None
174    }
175}
176
177// todo:  Rename MinWidth?
178
179fn get_increase_list<F>(
180    widths: &[usize],
181    need: usize,
182    mut current: usize,
183    mut peaker: F,
184) -> Vec<usize>
185where
186    F: Peaker,
187{
188    let mut widths = widths.to_vec();
189
190    while need != current {
191        let col = match peaker.peak(&[], &widths) {
192            Some(col) => col,
193            None => break,
194        };
195
196        widths[col] += 1;
197        current += 1;
198    }
199
200    widths
201}
202
203fn increase_width(s: &str, width: usize, fill_with: char) -> String {
204    let mut buf = String::new();
205    for (i, line) in get_lines(s).enumerate() {
206        if i > 0 {
207            buf.push('\n');
208        }
209
210        buf.push_str(&line);
211
212        let length = get_line_width(&line);
213        if length < width {
214            let remain = width - length;
215            buf.extend(repeat_n(fill_with, remain));
216        }
217    }
218
219    buf
220}