mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 19:27:44 +02:00
fix https://github.com/nushell/nushell/issues/7380
This commit is contained in:
@ -2,54 +2,10 @@ mod nu_protocol_table;
|
||||
mod table;
|
||||
mod table_theme;
|
||||
mod textstyle;
|
||||
mod util;
|
||||
|
||||
pub use nu_protocol_table::NuTable;
|
||||
pub use table::{Alignments, Table};
|
||||
pub use table::{Alignments, Table, TableConfig};
|
||||
pub use table_theme::TableTheme;
|
||||
pub use textstyle::{Alignment, TextStyle};
|
||||
|
||||
use tabled::{Padding, Style, Width};
|
||||
|
||||
pub fn string_width(text: &str) -> usize {
|
||||
tabled::papergrid::util::string_width_multiline_tab(text, 4)
|
||||
}
|
||||
|
||||
pub fn wrap_string(text: &str, width: usize) -> String {
|
||||
// well... it's not effitient to build a table to wrap a string,
|
||||
// but ... it's better than a copy paste
|
||||
tabled::builder::Builder::from_iter([[text]])
|
||||
.build()
|
||||
.with(Padding::zero())
|
||||
.with(Style::empty())
|
||||
.with(Width::wrap(width))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn string_truncate(text: &str, width: usize) -> String {
|
||||
// todo: change me...
|
||||
|
||||
match text.lines().next() {
|
||||
Some(first_line) => tabled::builder::Builder::from_iter([[first_line]])
|
||||
.build()
|
||||
.with(tabled::Style::empty())
|
||||
.with(tabled::Padding::zero())
|
||||
.with(tabled::Width::truncate(width))
|
||||
.to_string(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_wrap(text: &str, width: usize) -> String {
|
||||
// todo: change me...
|
||||
|
||||
if text.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
tabled::builder::Builder::from_iter([[text]])
|
||||
.build()
|
||||
.with(tabled::Style::empty())
|
||||
.with(tabled::Padding::zero())
|
||||
.with(tabled::Width::wrap(width))
|
||||
.to_string()
|
||||
}
|
||||
pub use util::*;
|
||||
|
@ -1,9 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nu_protocol::{Config, Span, Value};
|
||||
use tabled::{color::Color, papergrid::records::Records, Table};
|
||||
use tabled::{
|
||||
color::Color, formatting::AlignmentStrategy, object::Segment, papergrid::records::Records,
|
||||
Alignment, Modify, Table,
|
||||
};
|
||||
|
||||
use crate::{table::TrimStrategyModifier, TableTheme};
|
||||
use crate::{table::TrimStrategyModifier, Alignments, TableTheme};
|
||||
|
||||
/// NuTable has a recursive table representation of nu_prorocol::Value.
|
||||
///
|
||||
@ -215,4 +218,10 @@ fn load_theme<R>(
|
||||
table.with(color);
|
||||
}
|
||||
}
|
||||
|
||||
table.with(
|
||||
Modify::new(Segment::all())
|
||||
.with(Alignment::Horizontal(Alignments::default().data))
|
||||
.with(AlignmentStrategy::PerLine),
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
use std::{cmp::min, collections::HashMap, fmt::Display};
|
||||
|
||||
use nu_protocol::{Config, FooterMode, TrimStrategy};
|
||||
use nu_ansi_term::Style;
|
||||
use nu_protocol::TrimStrategy;
|
||||
use tabled::{
|
||||
alignment::AlignmentHorizontal,
|
||||
builder::Builder,
|
||||
@ -12,8 +13,11 @@ use tabled::{
|
||||
records::{
|
||||
cell_info::CellInfo, tcell::TCell, vec_records::VecRecords, Records, RecordsMut,
|
||||
},
|
||||
util::string_width_multiline,
|
||||
width::CfgWidthFunction,
|
||||
Estimate,
|
||||
},
|
||||
peaker::Peaker,
|
||||
Alignment, Modify, ModifyObject, TableOption, Width,
|
||||
};
|
||||
|
||||
@ -23,9 +27,6 @@ use crate::{table_theme::TableTheme, TextStyle};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Table {
|
||||
data: Data,
|
||||
is_empty: bool,
|
||||
with_header: bool,
|
||||
with_index: bool,
|
||||
}
|
||||
|
||||
type Data = VecRecords<TCell<CellInfo<'static>, TextStyle>>;
|
||||
@ -34,33 +35,22 @@ impl Table {
|
||||
/// Creates a [Table] instance.
|
||||
///
|
||||
/// If `headers.is_empty` then no headers will be rendered.
|
||||
pub fn new(
|
||||
mut data: Vec<Vec<TCell<CellInfo<'static>, TextStyle>>>,
|
||||
size: (usize, usize),
|
||||
termwidth: usize,
|
||||
with_header: bool,
|
||||
with_index: bool,
|
||||
) -> Table {
|
||||
pub fn new(data: Vec<Vec<TCell<CellInfo<'static>, TextStyle>>>, size: (usize, usize)) -> Table {
|
||||
// it's not guaranted that data will have all rows with the same number of columns.
|
||||
// but VecRecords::with_hint require this constrain.
|
||||
for row in &mut data {
|
||||
if row.len() < size.1 {
|
||||
row.extend(
|
||||
std::iter::repeat(Self::create_cell(String::default(), TextStyle::default()))
|
||||
.take(size.1 - row.len()),
|
||||
);
|
||||
}
|
||||
}
|
||||
//
|
||||
// so we do a check to make it certainly true
|
||||
|
||||
let mut data = VecRecords::with_hint(data, size.1);
|
||||
let is_empty = maybe_truncate_columns(&mut data, size.1, termwidth);
|
||||
let mut data = data;
|
||||
make_data_consistent(&mut data, size);
|
||||
|
||||
Table {
|
||||
data,
|
||||
is_empty,
|
||||
with_header,
|
||||
with_index,
|
||||
}
|
||||
let data = VecRecords::with_hint(data, size.1);
|
||||
|
||||
Table { data }
|
||||
}
|
||||
|
||||
pub fn count_rows(&self) -> usize {
|
||||
self.data.count_rows()
|
||||
}
|
||||
|
||||
pub fn create_cell(
|
||||
@ -70,29 +60,15 @@ impl Table {
|
||||
TCell::new(CellInfo::new(text.into(), CfgWidthFunction::new(4)), style)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.is_empty
|
||||
}
|
||||
|
||||
pub fn size(&self) -> (usize, usize) {
|
||||
(self.data.count_rows(), self.data.count_columns())
|
||||
}
|
||||
|
||||
pub fn is_with_index(&self) -> bool {
|
||||
self.with_index
|
||||
}
|
||||
|
||||
pub fn truncate(&mut self, width: usize, theme: &TableTheme) -> bool {
|
||||
let mut truncated = false;
|
||||
while self.data.count_rows() > 0 && self.data.count_columns() > 0 {
|
||||
let mut table = Builder::custom(self.data.clone()).build();
|
||||
load_theme(&mut table, &HashMap::new(), theme, false, false);
|
||||
let total = table.total_width();
|
||||
|
||||
// println!("{}", table);
|
||||
// println!("width={:?} total={:?}", width, total);
|
||||
|
||||
drop(table);
|
||||
let total;
|
||||
{
|
||||
let mut table = Builder::custom(self.data.clone()).build();
|
||||
load_theme(&mut table, theme, false, false, None);
|
||||
total = table.total_width();
|
||||
}
|
||||
|
||||
if total > width {
|
||||
truncated = true;
|
||||
@ -117,19 +93,69 @@ impl Table {
|
||||
false
|
||||
}
|
||||
|
||||
/// Draws a trable on a String.
|
||||
/// Converts a table to a String.
|
||||
///
|
||||
/// It returns None in case where table cannot be fit to a terminal width.
|
||||
pub fn draw_table(
|
||||
self,
|
||||
config: &Config,
|
||||
color_hm: &HashMap<String, nu_ansi_term::Style>,
|
||||
alignments: Alignments,
|
||||
theme: &TableTheme,
|
||||
termwidth: usize,
|
||||
expand: bool,
|
||||
) -> Option<String> {
|
||||
draw_table(self, config, color_hm, alignments, theme, termwidth, expand)
|
||||
pub fn draw(self, config: TableConfig, termwidth: usize) -> Option<String> {
|
||||
build_table(self.data, config, termwidth)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_data_consistent(data: &mut Vec<Vec<TCell<CellInfo, TextStyle>>>, size: (usize, usize)) {
|
||||
for row in data {
|
||||
if row.len() < size.1 {
|
||||
row.extend(
|
||||
std::iter::repeat(Table::create_cell(String::default(), TextStyle::default()))
|
||||
.take(size.1 - row.len()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableConfig {
|
||||
theme: TableTheme,
|
||||
alignments: Alignments,
|
||||
trim: TrimStrategy,
|
||||
split_color: Option<Style>,
|
||||
expand: bool,
|
||||
with_index: bool,
|
||||
with_header: bool,
|
||||
with_footer: bool,
|
||||
}
|
||||
|
||||
impl TableConfig {
|
||||
pub fn new(
|
||||
theme: TableTheme,
|
||||
with_header: bool,
|
||||
with_index: bool,
|
||||
append_footer: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
theme,
|
||||
with_header,
|
||||
with_index,
|
||||
with_footer: append_footer,
|
||||
expand: false,
|
||||
alignments: Alignments::default(),
|
||||
trim: TrimStrategy::truncate(None),
|
||||
split_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand(mut self) -> Self {
|
||||
self.expand = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trim(mut self, strategy: TrimStrategy) -> Self {
|
||||
self.trim = strategy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn splitline_style(mut self, color: Style) -> Self {
|
||||
self.split_color = Some(color);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,65 +176,63 @@ impl Default for Alignments {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_table(
|
||||
mut table: Table,
|
||||
config: &Config,
|
||||
color_hm: &HashMap<String, nu_ansi_term::Style>,
|
||||
alignments: Alignments,
|
||||
theme: &TableTheme,
|
||||
termwidth: usize,
|
||||
expand: bool,
|
||||
) -> Option<String> {
|
||||
if table.is_empty {
|
||||
fn build_table(mut data: Data, cfg: TableConfig, termwidth: usize) -> Option<String> {
|
||||
let priority = TruncationPriority::Content;
|
||||
let is_empty = maybe_truncate_columns(&mut data, &cfg.theme, termwidth, priority);
|
||||
|
||||
if is_empty {
|
||||
return None;
|
||||
}
|
||||
|
||||
let with_header = table.with_header;
|
||||
let with_footer = with_header && need_footer(config, (table.data).size().0 as u64);
|
||||
let with_index = table.with_index;
|
||||
|
||||
if with_footer {
|
||||
table.data.duplicate_row(0);
|
||||
if cfg.with_footer {
|
||||
data.duplicate_row(0);
|
||||
}
|
||||
|
||||
let mut table = Builder::custom(table.data).build();
|
||||
load_theme(&mut table, color_hm, theme, with_footer, with_header);
|
||||
draw_table(
|
||||
data,
|
||||
&cfg.theme,
|
||||
cfg.alignments,
|
||||
cfg.with_index,
|
||||
cfg.with_header,
|
||||
cfg.with_footer,
|
||||
cfg.expand,
|
||||
cfg.split_color,
|
||||
&cfg.trim,
|
||||
termwidth,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_table(
|
||||
data: Data,
|
||||
theme: &TableTheme,
|
||||
alignments: Alignments,
|
||||
with_index: bool,
|
||||
with_header: bool,
|
||||
with_footer: bool,
|
||||
expand: bool,
|
||||
split_color: Option<Style>,
|
||||
trim_strategy: &TrimStrategy,
|
||||
termwidth: usize,
|
||||
) -> Option<String> {
|
||||
let mut table = Builder::custom(data).build();
|
||||
load_theme(&mut table, theme, with_footer, with_header, split_color);
|
||||
align_table(&mut table, alignments, with_index, with_header, with_footer);
|
||||
|
||||
if expand {
|
||||
table.with(Width::increase(termwidth));
|
||||
}
|
||||
|
||||
table_trim_columns(&mut table, termwidth, &config.trim_strategy);
|
||||
table_trim_columns(&mut table, termwidth, trim_strategy);
|
||||
|
||||
let table = print_table(table, config);
|
||||
if table_width(&table) > termwidth {
|
||||
let text = table.to_string();
|
||||
if string_width_multiline(&text) > termwidth {
|
||||
None
|
||||
} else {
|
||||
Some(table)
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_table(table: tabled::Table<Data>, config: &Config) -> String {
|
||||
let output = table.to_string();
|
||||
|
||||
// the atty is for when people do ls from vim, there should be no coloring there
|
||||
if !config.use_ansi_coloring || !atty::is(atty::Stream::Stdout) {
|
||||
// Draw the table without ansi colors
|
||||
nu_utils::strip_ansi_string_likely(output)
|
||||
} else {
|
||||
// Draw the table with ansi colors
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn table_width(table: &str) -> usize {
|
||||
table
|
||||
.lines()
|
||||
.next()
|
||||
.map_or(0, papergrid::util::string_width)
|
||||
}
|
||||
|
||||
fn align_table(
|
||||
table: &mut tabled::Table<Data>,
|
||||
alignments: Alignments,
|
||||
@ -268,10 +292,10 @@ fn override_alignments(
|
||||
|
||||
fn load_theme<R>(
|
||||
table: &mut tabled::Table<R>,
|
||||
color_hm: &HashMap<String, nu_ansi_term::Style>,
|
||||
theme: &TableTheme,
|
||||
with_footer: bool,
|
||||
with_header: bool,
|
||||
separator_color: Option<Style>,
|
||||
) where
|
||||
R: Records,
|
||||
{
|
||||
@ -282,7 +306,7 @@ fn load_theme<R>(
|
||||
|
||||
table.with(theme);
|
||||
|
||||
if let Some(color) = color_hm.get("separator") {
|
||||
if let Some(color) = separator_color {
|
||||
let color = color.paint(" ").to_string();
|
||||
if let Ok(color) = Color::try_from(color) {
|
||||
table.with(color);
|
||||
@ -298,11 +322,6 @@ fn load_theme<R>(
|
||||
}
|
||||
}
|
||||
|
||||
fn need_footer(config: &Config, count_records: u64) -> bool {
|
||||
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
||||
|| matches!(config.footer_mode, FooterMode::Always)
|
||||
}
|
||||
|
||||
struct FooterStyle;
|
||||
|
||||
impl<R> TableOption<R> for FooterStyle
|
||||
@ -352,7 +371,7 @@ where
|
||||
fn change(&mut self, table: &mut tabled::Table<R>) {
|
||||
match self.trim_strategy {
|
||||
TrimStrategy::Wrap { try_to_keep_words } => {
|
||||
let mut w = Width::wrap(self.termwidth).priority::<tabled::peaker::PriorityMax>();
|
||||
let mut w = Width::wrap(self.termwidth).priority::<PriorityMax>();
|
||||
if *try_to_keep_words {
|
||||
w = w.keep_words();
|
||||
}
|
||||
@ -360,8 +379,7 @@ where
|
||||
w.change(table)
|
||||
}
|
||||
TrimStrategy::Truncate { suffix } => {
|
||||
let mut w =
|
||||
Width::truncate(self.termwidth).priority::<tabled::peaker::PriorityMax>();
|
||||
let mut w = Width::truncate(self.termwidth).priority::<PriorityMax>();
|
||||
if let Some(suffix) = suffix {
|
||||
w = w.suffix(suffix).suffix_try_color(true);
|
||||
}
|
||||
@ -372,20 +390,188 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_truncate_columns(data: &mut Data, length: usize, termwidth: usize) -> bool {
|
||||
// Make sure we have enough space for the columns we have
|
||||
let max_num_of_columns = termwidth / 10;
|
||||
if max_num_of_columns == 0 {
|
||||
enum TruncationPriority {
|
||||
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
||||
Content,
|
||||
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
|
||||
//
|
||||
// not used so far.
|
||||
#[allow(dead_code)]
|
||||
Columns,
|
||||
}
|
||||
|
||||
fn maybe_truncate_columns(
|
||||
data: &mut Data,
|
||||
theme: &TableTheme,
|
||||
termwidth: usize,
|
||||
priority: TruncationPriority,
|
||||
) -> bool {
|
||||
if data.count_columns() == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have too many columns, truncate the table
|
||||
if max_num_of_columns < length {
|
||||
data.truncate(max_num_of_columns);
|
||||
data.push(Table::create_cell(
|
||||
String::from("..."),
|
||||
TextStyle::default(),
|
||||
));
|
||||
match priority {
|
||||
TruncationPriority::Content => truncate_columns_by_content(data, theme, termwidth),
|
||||
TruncationPriority::Columns => truncate_columns_by_columns(data, theme, termwidth),
|
||||
}
|
||||
}
|
||||
|
||||
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
||||
fn truncate_columns_by_content(data: &mut Data, theme: &TableTheme, termwidth: usize) -> bool {
|
||||
const MIN_ACCEPTABLE_WIDTH: usize = 3;
|
||||
const TRAILING_COLUMN_WIDTH: usize = 5;
|
||||
const TRAILING_COLUMN_STR: &str = "...";
|
||||
|
||||
let config;
|
||||
let total;
|
||||
{
|
||||
let mut table = Builder::custom(&*data).build();
|
||||
load_theme(&mut table, theme, false, false, None);
|
||||
total = table.total_width();
|
||||
config = table.get_config().clone();
|
||||
}
|
||||
|
||||
if total <= termwidth {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut width_ctrl = tabled::papergrid::width::WidthEstimator::default();
|
||||
width_ctrl.estimate(&*data, &config);
|
||||
let widths = Vec::from(width_ctrl);
|
||||
|
||||
let borders = config.get_borders();
|
||||
let vertical_border_i = borders.has_vertical() as usize;
|
||||
|
||||
let mut width = borders.has_left() as usize + borders.has_right() as usize;
|
||||
let mut truncate_pos = 0;
|
||||
for column_width in widths {
|
||||
width += column_width;
|
||||
width += vertical_border_i;
|
||||
|
||||
if width >= termwidth {
|
||||
// check whether we CAN limit the column width
|
||||
width -= column_width;
|
||||
width += MIN_ACCEPTABLE_WIDTH;
|
||||
|
||||
if width <= termwidth {
|
||||
truncate_pos += 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
truncate_pos += 1;
|
||||
}
|
||||
|
||||
// we don't need any truncation then (is it possible?)
|
||||
if truncate_pos + 1 == data.count_columns() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if truncate_pos == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.truncate(truncate_pos);
|
||||
|
||||
// Append columns with a trailing column
|
||||
|
||||
let min_width = borders.has_left() as usize
|
||||
+ borders.has_right() as usize
|
||||
+ data.count_columns() * MIN_ACCEPTABLE_WIDTH
|
||||
+ (data.count_columns() - 1) * vertical_border_i;
|
||||
|
||||
let diff = termwidth - min_width;
|
||||
let can_be_squeezed = diff > TRAILING_COLUMN_WIDTH + vertical_border_i;
|
||||
|
||||
if can_be_squeezed {
|
||||
let cell = Table::create_cell(String::from(TRAILING_COLUMN_STR), TextStyle::default());
|
||||
data.push(cell);
|
||||
} else {
|
||||
if data.count_columns() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.truncate(data.count_columns() - 1);
|
||||
|
||||
let cell = Table::create_cell(String::from(TRAILING_COLUMN_STR), TextStyle::default());
|
||||
data.push(cell);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn truncate_columns_by_columns(data: &mut Data, theme: &TableTheme, termwidth: usize) -> bool {
|
||||
const MIN_ACCEPTABLE_WIDTH: usize = 3;
|
||||
const TRAILING_COLUMN_WIDTH: usize = 3;
|
||||
const TRAILING_COLUMN_PADDING: usize = 2;
|
||||
const TRAILING_COLUMN_STR: &str = "...";
|
||||
|
||||
let config;
|
||||
let total;
|
||||
{
|
||||
let mut table = Builder::custom(&*data).build();
|
||||
load_theme(&mut table, theme, false, false, None);
|
||||
total = table.total_width();
|
||||
config = table.get_config().clone();
|
||||
}
|
||||
|
||||
if total <= termwidth {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut width_ctrl = tabled::papergrid::width::WidthEstimator::default();
|
||||
width_ctrl.estimate(&*data, &config);
|
||||
let widths = Vec::from(width_ctrl);
|
||||
let widths_total = widths.iter().sum::<usize>();
|
||||
|
||||
let min_widths = widths
|
||||
.iter()
|
||||
.map(|w| min(*w, MIN_ACCEPTABLE_WIDTH))
|
||||
.sum::<usize>();
|
||||
let mut min_total = total - widths_total + min_widths;
|
||||
|
||||
if min_total <= termwidth {
|
||||
return false;
|
||||
}
|
||||
|
||||
while data.count_columns() > 0 {
|
||||
let column = data.count_columns() - 1;
|
||||
|
||||
data.truncate(column);
|
||||
|
||||
let width = widths[column];
|
||||
let min_width = min(width, MIN_ACCEPTABLE_WIDTH);
|
||||
|
||||
min_total -= min_width;
|
||||
if config.get_borders().has_vertical() {
|
||||
min_total -= 1;
|
||||
}
|
||||
|
||||
if min_total <= termwidth {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if data.count_columns() == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Append columns with a trailing column
|
||||
|
||||
let diff = termwidth - min_total;
|
||||
if diff > TRAILING_COLUMN_WIDTH + TRAILING_COLUMN_PADDING {
|
||||
let cell = Table::create_cell(String::from(TRAILING_COLUMN_STR), TextStyle::default());
|
||||
data.push(cell);
|
||||
} else {
|
||||
if data.count_columns() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
data.truncate(data.count_columns() - 1);
|
||||
|
||||
let cell = Table::create_cell(String::from(TRAILING_COLUMN_STR), TextStyle::default());
|
||||
data.push(cell);
|
||||
}
|
||||
|
||||
false
|
||||
@ -410,3 +596,27 @@ impl papergrid::Color for TextStyle {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PriorityMax;
|
||||
|
||||
impl Peaker for PriorityMax {
|
||||
fn create() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> {
|
||||
let col = (0..widths.len()).rev().max_by_key(|&i| widths[i]);
|
||||
match col {
|
||||
Some(col) => {
|
||||
if widths[col] == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(col)
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
54
crates/nu-table/src/util.rs
Normal file
54
crates/nu-table/src/util.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use tabled::{builder::Builder, Padding, Style, Width};
|
||||
|
||||
pub fn string_width(text: &str) -> usize {
|
||||
tabled::papergrid::util::string_width_multiline_tab(text, 4)
|
||||
}
|
||||
|
||||
pub fn wrap_string(text: &str, width: usize) -> String {
|
||||
// todo: change me...
|
||||
//
|
||||
// well... it's not effitient to build a table to wrap a string,
|
||||
// but ... it's better than a copy paste (is it?)
|
||||
|
||||
if text.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
Builder::from_iter([[text]])
|
||||
.build()
|
||||
.with(Padding::zero())
|
||||
.with(Style::empty())
|
||||
.with(Width::wrap(width))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn string_truncate(text: &str, width: usize) -> String {
|
||||
// todo: change me...
|
||||
|
||||
let line = match text.lines().next() {
|
||||
Some(first_line) => first_line,
|
||||
None => return String::new(),
|
||||
};
|
||||
|
||||
Builder::from_iter([[line]])
|
||||
.build()
|
||||
.with(Style::empty())
|
||||
.with(Padding::zero())
|
||||
.with(Width::truncate(width))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn string_wrap(text: &str, width: usize) -> String {
|
||||
// todo: change me...
|
||||
|
||||
if text.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
Builder::from_iter([[text]])
|
||||
.build()
|
||||
.with(Style::empty())
|
||||
.with(Padding::zero())
|
||||
.with(Width::wrap(width))
|
||||
.to_string()
|
||||
}
|
Reference in New Issue
Block a user