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