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 | |
5 | use std::marker::PhantomData; |
6 | |
7 | use 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 | |
19 | use 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)] |
57 | pub struct MinWidth<W = usize, P = PriorityNone> { |
58 | width: W, |
59 | fill: char, |
60 | _priority: PhantomData<P>, |
61 | } |
62 | |
63 | impl<W> MinWidth<W> |
64 | where |
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 | |
77 | impl<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 | |
104 | impl<W, R> CellOption<R, ColoredConfig> for MinWidth<W> |
105 | where |
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 | |
139 | impl<W, P, R> TableOption<R, ColoredConfig, CompleteDimensionVecRecords<'_>> for MinWidth<W, P> |
140 | where |
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 | |
173 | fn get_increase_list<F>( |
174 | mut widths: Vec<usize>, |
175 | need: usize, |
176 | mut current: usize, |
177 | mut peaker: F, |
178 | ) -> Vec<usize> |
179 | where |
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 | |
195 | fn 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 | |