| 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 | |