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