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, |
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 | |
20 | use 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)] |
58 | pub struct MinWidth<W = usize, P = PriorityNone> { |
59 | width: W, |
60 | fill: char, |
61 | _priority: PhantomData<P>, |
62 | } |
63 | |
64 | impl<W> MinWidth<W> |
65 | where |
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 | |
78 | impl<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 | |
105 | impl<W, R> CellOption<R, ColoredConfig> for MinWidth<W> |
106 | where |
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 | |
135 | impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for MinWidth<W, P> |
136 | where |
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 | |
164 | fn get_increase_list<F>( |
165 | mut widths: Vec<usize>, |
166 | need: usize, |
167 | mut current: usize, |
168 | mut peaker: F, |
169 | ) -> Vec<usize> |
170 | where |
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 | |
186 | fn 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 | |