tabled/features/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
3use std::marker::PhantomData;
4
5use papergrid::{
6    records::{Records, RecordsMut},
7    width::CfgWidthFunction,
8    Entity,
9};
10
11use crate::{
12    measurment::Measurment,
13    peaker::{Peaker, PriorityNone},
14    CellOption, Table, TableOption, Width,
15};
16
17use super::get_table_widths_with_total;
18
19/// [`MinWidth`] changes a content in case if it's length is lower then the boundary.
20///
21/// It can be applied to a whole table.
22///
23/// It does nothing in case if the content's length is bigger then the boundary.
24///
25/// Be aware that further changes of the table may cause the width being not set.
26/// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect.
27/// (You should use [`Padding`] first).
28///
29/// Be aware that it doesn't consider padding.
30/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
31///
32/// ## Examples
33///
34/// Cell change
35///
36/// ```
37/// use tabled::{object::Segment, Width, Modify, Style, Table};
38///
39/// let data = ["Hello", "World", "!"];
40///
41/// let table = Table::new(&data)
42///     .with(Style::markdown())
43///     .with(Modify::new(Segment::all()).with(Width::increase(10)));
44/// ```
45/// Table change
46///
47/// ```
48/// use tabled::{Width, Table};
49///
50/// let table = Table::new(&["Hello World!"]).with(Width::increase(5));
51/// ```
52///
53/// [`Padding`]: crate::Padding
54#[derive(Debug)]
55pub struct MinWidth<W = usize, P = PriorityNone> {
56    width: W,
57    fill: char,
58    _priority: PhantomData<P>,
59}
60
61impl<W> MinWidth<W>
62where
63    W: Measurment<Width>,
64{
65    /// Creates a new instance of [`MinWidth`].
66    pub fn new(width: W) -> Self {
67        Self {
68            width,
69            fill: ' ',
70            _priority: PhantomData::default(),
71        }
72    }
73}
74
75impl<W, P> MinWidth<W, P> {
76    /// Set's a fill character which will be used to fill the space
77    /// when increasing the length of the string to the set boundary.
78    ///
79    /// Used only if chaning cells.
80    pub fn fill_with(mut self, c: char) -> Self {
81        self.fill = c;
82        self
83    }
84
85    /// Priority defines the logic by which a increase of width will be applied when is done for the whole table.
86    ///
87    /// - [`PriorityNone`] which inc the columns one after another.
88    /// - [`PriorityMax`] inc the biggest columns first.
89    /// - [`PriorityMin`] inc the lowest columns first.
90    ///
91    /// [`PriorityMax`]: crate::peaker::PriorityMax
92    /// [`PriorityMin`]: crate::peaker::PriorityMin
93    pub fn priority<PP: Peaker>(self) -> MinWidth<W, PP> {
94        MinWidth {
95            fill: self.fill,
96            width: self.width,
97            _priority: PhantomData::default(),
98        }
99    }
100}
101
102impl<W, R> CellOption<R> for MinWidth<W>
103where
104    W: Measurment<Width>,
105    R: Records + RecordsMut<String>,
106{
107    fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
108        let width_ctrl = CfgWidthFunction::from_cfg(table.get_config());
109        let width = self.width.measure(table.get_records(), table.get_config());
110
111        let (count_rows, count_cols) = table.shape();
112        for pos in entity.iter(count_rows, count_cols) {
113            let records = table.get_records();
114            let cell_width = records.get_width(pos, &width_ctrl);
115            if cell_width >= width {
116                continue;
117            }
118
119            let content = records.get_text(pos);
120            let content = increase_width(content, width, self.fill);
121            let records = table.get_records_mut();
122            records.set(pos, content, &width_ctrl);
123        }
124
125        table.destroy_width_cache();
126    }
127}
128
129impl<W, P, R> TableOption<R> for MinWidth<W, P>
130where
131    W: Measurment<Width>,
132    P: Peaker,
133    R: Records + RecordsMut<String>,
134{
135    fn change(&mut self, table: &mut Table<R>) {
136        if table.is_empty() {
137            return;
138        }
139
140        let width = self.width.measure(table.get_records(), table.get_config());
141        let (widths, total_width) =
142            get_table_widths_with_total(table.get_records(), table.get_config());
143        if total_width >= width {
144            return;
145        }
146
147        increase_total_width(table, widths, total_width, width, P::create());
148    }
149}
150
151#[cfg(not(feature = "color"))]
152fn increase_width(s: &str, width: usize, fill_with: char) -> String {
153    use papergrid::util::string_width;
154
155    s.lines()
156        .map(|line| {
157            let length = string_width(line);
158            if width > length {
159                let remain = width - length;
160                let mut new_line = String::with_capacity(width);
161                new_line.push_str(line);
162                new_line.extend(std::iter::repeat(fill_with).take(remain));
163                std::borrow::Cow::Owned(new_line)
164            } else {
165                std::borrow::Cow::Borrowed(line)
166            }
167        })
168        .collect::<Vec<_>>()
169        .join("\n")
170}
171
172#[cfg(feature = "color")]
173fn increase_width(s: &str, width: usize, fill_with: char) -> String {
174    use papergrid::util::string_width;
175
176    ansi_str::AnsiStr::ansi_split(s, "\n")
177        .map(|line| {
178            let length = string_width(&line);
179            if length < width {
180                let mut line = line.into_owned();
181                let remain = width - length;
182                line.extend(std::iter::repeat(fill_with).take(remain));
183                std::borrow::Cow::Owned(line)
184            } else {
185                line
186            }
187        })
188        .collect::<Vec<_>>()
189        .join("\n")
190}
191
192fn increase_total_width<P, R>(
193    table: &mut Table<R>,
194    widths: Vec<usize>,
195    total_width: usize,
196    expected_width: usize,
197    priority: P,
198) where
199    P: Peaker,
200    R: Records + RecordsMut<String>,
201{
202    let increase_list = get_increase_list(widths, expected_width, total_width, priority);
203    table.cache_width(increase_list);
204}
205
206fn get_increase_list<F>(
207    mut widths: Vec<usize>,
208    total_width: usize,
209    mut width: usize,
210    mut peaker: F,
211) -> Vec<usize>
212where
213    F: Peaker,
214{
215    while width != total_width {
216        let col = match peaker.peak(&[], &widths) {
217            Some(col) => col,
218            None => break,
219        };
220
221        widths[col] += 1;
222        width += 1;
223    }
224
225    widths
226}