summaryrefslogtreecommitdiffstats
path: root/vendor/tabled/src/settings/width/truncate.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
commitef24de24a82fe681581cc130f342363c47c0969a (patch)
tree0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/tabled/src/settings/width/truncate.rs
parentReleasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz
rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/tabled/src/settings/width/truncate.rs')
-rw-r--r--vendor/tabled/src/settings/width/truncate.rs505
1 files changed, 505 insertions, 0 deletions
diff --git a/vendor/tabled/src/settings/width/truncate.rs b/vendor/tabled/src/settings/width/truncate.rs
new file mode 100644
index 000000000..c7336f037
--- /dev/null
+++ b/vendor/tabled/src/settings/width/truncate.rs
@@ -0,0 +1,505 @@
+//! This module contains [`Truncate`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by truncating the width.
+//!
+//! [`Table`]: crate::Table
+
+use std::{borrow::Cow, iter, marker::PhantomData};
+
+use crate::{
+ grid::{
+ config::{ColoredConfig, SpannedConfig},
+ dimension::CompleteDimensionVecRecords,
+ records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut},
+ util::string::{string_width, string_width_multiline},
+ },
+ settings::{
+ measurement::Measurement,
+ peaker::{Peaker, PriorityNone},
+ CellOption, TableOption, Width,
+ },
+};
+
+use super::util::{cut_str, get_table_widths, get_table_widths_with_total};
+
+/// Truncate cut the string to a given width if its length exceeds it.
+/// Otherwise keeps the content of a cell untouched.
+///
+/// The function is color aware if a `color` feature is on.
+///
+/// Be aware that it doesn't consider padding.
+/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
+///
+/// ## Example
+///
+/// ```
+/// use tabled::{Table, settings::{object::Segment, Width, Modify}};
+///
+/// let table = Table::new(&["Hello World!"])
+/// .with(Modify::new(Segment::all()).with(Width::truncate(3)));
+/// ```
+///
+/// [`Padding`]: crate::settings::Padding
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Truncate<'a, W = usize, P = PriorityNone> {
+ width: W,
+ suffix: Option<TruncateSuffix<'a>>,
+ multiline: bool,
+ _priority: PhantomData<P>,
+}
+#[cfg(feature = "color")]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+struct TruncateSuffix<'a> {
+ text: Cow<'a, str>,
+ limit: SuffixLimit,
+ try_color: bool,
+}
+
+#[cfg(not(feature = "color"))]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+struct TruncateSuffix<'a> {
+ text: Cow<'a, str>,
+ limit: SuffixLimit,
+}
+
+impl Default for TruncateSuffix<'_> {
+ fn default() -> Self {
+ Self {
+ text: Cow::default(),
+ limit: SuffixLimit::Cut,
+ #[cfg(feature = "color")]
+ try_color: false,
+ }
+ }
+}
+
+/// A suffix limit settings.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum SuffixLimit {
+ /// Cut the suffix.
+ Cut,
+ /// Don't show the suffix.
+ Ignore,
+ /// Use a string with n chars instead.
+ Replace(char),
+}
+
+impl<W> Truncate<'static, W>
+where
+ W: Measurement<Width>,
+{
+ /// Creates a [`Truncate`] object
+ pub fn new(width: W) -> Truncate<'static, W> {
+ Self {
+ width,
+ multiline: false,
+ suffix: None,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<'a, W, P> Truncate<'a, W, P> {
+ /// Sets a suffix which will be appended to a resultant string.
+ ///
+ /// The suffix is used in 3 circumstances:
+ /// 1. If original string is *bigger* than the suffix.
+ /// We cut more of the original string and append the suffix.
+ /// 2. If suffix is bigger than the original string.
+ /// We cut the suffix to fit in the width by default.
+ /// But you can peak the behaviour by using [`Truncate::suffix_limit`]
+ pub fn suffix<S: Into<Cow<'a, str>>>(self, suffix: S) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.text = suffix.into();
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+
+ /// Sets a suffix limit, which is used when the suffix is too big to be used.
+ pub fn suffix_limit(self, limit: SuffixLimit) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.limit = limit;
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+
+ /// Use trancate logic per line, not as a string as a whole.
+ pub fn multiline(self) -> Truncate<'a, W, P> {
+ Truncate {
+ width: self.width,
+ multiline: true,
+ suffix: self.suffix,
+ _priority: self._priority,
+ }
+ }
+
+ #[cfg(feature = "color")]
+ /// Sets a optional logic to try to colorize a suffix.
+ pub fn suffix_try_color(self, color: bool) -> Truncate<'a, W, P> {
+ let mut suff = self.suffix.unwrap_or_default();
+ suff.try_color = color;
+
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: Some(suff),
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl<'a, W, P> Truncate<'a, W, P> {
+ /// Priority defines the logic by which a truncate will be applied when is done for the whole table.
+ ///
+ /// - [`PriorityNone`] which cuts the columns one after another.
+ /// - [`PriorityMax`] cuts the biggest columns first.
+ /// - [`PriorityMin`] cuts the lowest columns first.
+ ///
+ /// [`PriorityMax`]: crate::settings::peaker::PriorityMax
+ /// [`PriorityMin`]: crate::settings::peaker::PriorityMin
+ pub fn priority<PP: Peaker>(self) -> Truncate<'a, W, PP> {
+ Truncate {
+ width: self.width,
+ multiline: self.multiline,
+ suffix: self.suffix,
+ _priority: PhantomData,
+ }
+ }
+}
+
+impl Truncate<'_, (), ()> {
+ /// Truncate a given string
+ pub fn truncate_text(text: &str, width: usize) -> Cow<'_, str> {
+ truncate_text(text, width, "", false)
+ }
+}
+
+impl<W, P, R> CellOption<R, ColoredConfig> for Truncate<'_, W, P>
+where
+ W: Measurement<Width>,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: papergrid::config::Entity) {
+ let available = self.width.measure(&*records, cfg);
+
+ let mut width = available;
+ let mut suffix = Cow::Borrowed("");
+
+ if let Some(x) = self.suffix.as_ref() {
+ let (cutted_suffix, rest_width) = make_suffix(x, width);
+ suffix = cutted_suffix;
+ width = rest_width;
+ };
+
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let colorize = need_suffix_color_preservation(&self.suffix);
+
+ for pos in entity.iter(count_rows, count_columns) {
+ let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns;
+ if !is_valid_pos {
+ continue;
+ }
+
+ let text = records.get_text(pos);
+
+ let cell_width = string_width_multiline(text);
+ if available >= cell_width {
+ continue;
+ }
+
+ let text =
+ truncate_multiline(text, &suffix, width, available, colorize, self.multiline);
+
+ records.set(pos, text.into_owned());
+ }
+ }
+}
+
+fn truncate_multiline<'a>(
+ text: &'a str,
+ suffix: &'a str,
+ width: usize,
+ twidth: usize,
+ suffix_color: bool,
+ multiline: bool,
+) -> Cow<'a, str> {
+ if multiline {
+ let mut buf = String::new();
+ for (i, line) in crate::grid::util::string::get_lines(text).enumerate() {
+ if i != 0 {
+ buf.push('\n');
+ }
+
+ let line = make_text_truncated(&line, suffix, width, twidth, suffix_color);
+ buf.push_str(&line);
+ }
+
+ Cow::Owned(buf)
+ } else {
+ make_text_truncated(text, suffix, width, twidth, suffix_color)
+ }
+}
+
+fn make_text_truncated<'a>(
+ text: &'a str,
+ suffix: &'a str,
+ width: usize,
+ twidth: usize,
+ suffix_color: bool,
+) -> Cow<'a, str> {
+ if width == 0 {
+ if twidth == 0 {
+ Cow::Borrowed("")
+ } else {
+ Cow::Borrowed(suffix)
+ }
+ } else {
+ truncate_text(text, width, suffix, suffix_color)
+ }
+}
+
+fn need_suffix_color_preservation(_suffix: &Option<TruncateSuffix<'_>>) -> bool {
+ #[cfg(not(feature = "color"))]
+ {
+ false
+ }
+ #[cfg(feature = "color")]
+ {
+ _suffix.as_ref().map_or(false, |s| s.try_color)
+ }
+}
+
+fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) {
+ let suffix_length = string_width(&suffix.text);
+ if width > suffix_length {
+ return (Cow::Borrowed(suffix.text.as_ref()), width - suffix_length);
+ }
+
+ match suffix.limit {
+ SuffixLimit::Ignore => (Cow::Borrowed(""), width),
+ SuffixLimit::Cut => {
+ let suffix = cut_str(&suffix.text, width);
+ (suffix, 0)
+ }
+ SuffixLimit::Replace(c) => {
+ let suffix = Cow::Owned(iter::repeat(c).take(width).collect());
+ (suffix, 0)
+ }
+ }
+}
+
+impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig>
+ for Truncate<'_, W, P>
+where
+ W: Measurement<Width>,
+ P: Peaker,
+ R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+ for<'a> &'a R: Records,
+{
+ fn change(
+ self,
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ dims: &mut CompleteDimensionVecRecords<'static>,
+ ) {
+ if records.count_rows() == 0 || records.count_columns() == 0 {
+ return;
+ }
+
+ let width = self.width.measure(&*records, cfg);
+ let (widths, total) = get_table_widths_with_total(&*records, cfg);
+ if total <= width {
+ return;
+ }
+
+ let suffix = self.suffix.as_ref().map(|s| TruncateSuffix {
+ text: Cow::Borrowed(&s.text),
+ limit: s.limit,
+ #[cfg(feature = "color")]
+ try_color: s.try_color,
+ });
+
+ let priority = P::create();
+ let multiline = self.multiline;
+ let widths = truncate_total_width(
+ records, cfg, widths, total, width, priority, suffix, multiline,
+ );
+
+ let _ = dims.set_widths(widths);
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn truncate_total_width<P, R>(
+ records: &mut R,
+ cfg: &mut ColoredConfig,
+ mut widths: Vec<usize>,
+ total: usize,
+ width: usize,
+ priority: P,
+ suffix: Option<TruncateSuffix<'_>>,
+ multiline: bool,
+) -> Vec<usize>
+where
+ for<'a> &'a R: Records,
+ P: Peaker,
+ R: Records + PeekableRecords + ExactRecords + RecordsMut<String>,
+{
+ let count_rows = records.count_rows();
+ let count_columns = records.count_columns();
+
+ let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_columns), cfg);
+
+ decrease_widths(&mut widths, &min_widths, total, width, priority);
+
+ let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_columns));
+
+ for ((row, col), width) in points {
+ let mut truncate = Truncate::new(width);
+ truncate.suffix = suffix.clone();
+ truncate.multiline = multiline;
+ CellOption::change(truncate, records, cfg, (row, col).into());
+ }
+
+ widths
+}
+
+fn truncate_text<'a>(
+ text: &'a str,
+ width: usize,
+ suffix: &str,
+ _suffix_color: bool,
+) -> Cow<'a, str> {
+ let content = cut_str(text, width);
+ if suffix.is_empty() {
+ return content;
+ }
+
+ #[cfg(feature = "color")]
+ {
+ if _suffix_color {
+ if let Some(block) = ansi_str::get_blocks(text).last() {
+ if block.has_ansi() {
+ let style = block.style();
+ Cow::Owned(format!(
+ "{}{}{}{}",
+ content,
+ style.start(),
+ suffix,
+ style.end()
+ ))
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ } else {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+ }
+
+ #[cfg(not(feature = "color"))]
+ {
+ let mut content = content.into_owned();
+ content.push_str(suffix);
+ Cow::Owned(content)
+ }
+}
+
+fn get_decrease_cell_list(
+ cfg: &SpannedConfig,
+ widths: &[usize],
+ min_widths: &[usize],
+ shape: (usize, usize),
+) -> Vec<((usize, usize), usize)> {
+ let mut points = Vec::new();
+ (0..shape.1).for_each(|col| {
+ (0..shape.0)
+ .filter(|&row| cfg.is_cell_visible((row, col)))
+ .for_each(|row| {
+ let (width, width_min) = match cfg.get_column_span((row, col)) {
+ Some(span) => {
+ let width = (col..col + span).map(|i| widths[i]).sum::<usize>();
+ let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>();
+ let count_borders = count_borders(cfg, col, col + span, shape.1);
+ (width + count_borders, min_width + count_borders)
+ }
+ None => (widths[col], min_widths[col]),
+ };
+
+ if width >= width_min {
+ let padding = cfg.get_padding((row, col).into());
+ let width = width.saturating_sub(padding.left.size + padding.right.size);
+
+ points.push(((row, col), width));
+ }
+ });
+ });
+
+ points
+}
+
+fn decrease_widths<F>(
+ widths: &mut [usize],
+ min_widths: &[usize],
+ total_width: usize,
+ mut width: usize,
+ mut peeaker: F,
+) where
+ F: Peaker,
+{
+ let mut empty_list = 0;
+ for col in 0..widths.len() {
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+ }
+
+ while width != total_width {
+ if empty_list == widths.len() {
+ break;
+ }
+
+ let col = match peeaker.peak(min_widths, widths) {
+ Some(col) => col,
+ None => break,
+ };
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ continue;
+ }
+
+ widths[col] -= 1;
+
+ if widths[col] == 0 || widths[col] <= min_widths[col] {
+ empty_list += 1;
+ }
+
+ width += 1;
+ }
+}
+
+fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize {
+ (start..end)
+ .skip(1)
+ .filter(|&i| cfg.has_vertical(i, count_columns))
+ .count()
+}