nu_table/
common.rs

1use crate::{TableOutput, TableTheme, clean_charset, colorize_space_str, string_wrap};
2use nu_color_config::{Alignment, StyleComputer, TextStyle};
3use nu_protocol::{Config, FooterMode, ShellError, Span, TableMode, TrimStrategy, Value};
4use nu_utils::terminal_size;
5
6pub type NuText = (String, TextStyle);
7pub type TableResult = Result<Option<TableOutput>, ShellError>;
8pub type StringResult = Result<Option<String>, ShellError>;
9
10pub const INDEX_COLUMN_NAME: &str = "index";
11
12pub fn configure_table(
13    out: &mut TableOutput,
14    config: &Config,
15    comp: &StyleComputer,
16    mode: TableMode,
17) {
18    let with_footer = is_footer_needed(config, out);
19    let theme = load_theme(mode);
20
21    out.table.set_theme(theme);
22    out.table
23        .set_structure(out.with_index, out.with_header, with_footer);
24    out.table.set_trim(config.table.trim.clone());
25    out.table
26        .set_border_header(config.table.header_on_separator);
27    out.table.set_border_color(lookup_separator_color(comp));
28}
29
30fn is_footer_needed(config: &Config, out: &TableOutput) -> bool {
31    let mut count_rows = out.table.count_rows();
32    if config.table.footer_inheritance {
33        count_rows = out.count_rows;
34    }
35
36    with_footer(config, out.with_header, count_rows)
37}
38
39pub fn nu_value_to_string_colored(val: &Value, cfg: &Config, comp: &StyleComputer) -> String {
40    let (mut text, style) = nu_value_to_string(val, cfg, comp);
41
42    let is_string = matches!(val, Value::String { .. });
43    if is_string {
44        text = clean_charset(&text);
45    }
46
47    if let Some(color) = style.color_style {
48        text = color.paint(text).to_string();
49    }
50
51    if is_string {
52        colorize_space_str(&mut text, comp);
53    }
54
55    text
56}
57
58pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
59    let float_precision = cfg.float_precision as usize;
60    let text = val.to_abbreviated_string(cfg);
61    make_styled_value(text, val, float_precision, style)
62}
63
64// todo: Expose a method which returns just style
65
66pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleComputer) -> NuText {
67    let (text, style) = nu_value_to_string(val, cfg, style_comp);
68    let mut text = clean_charset(&text);
69    colorize_space_str(&mut text, style_comp);
70
71    (text, style)
72}
73
74pub fn error_sign(text: String, style_computer: &StyleComputer) -> (String, TextStyle) {
75    // Though holes are not the same as null, the closure for "empty" is passed a null anyway.
76
77    let style = style_computer.compute("empty", &Value::nothing(Span::unknown()));
78    (text, TextStyle::with_style(Alignment::Center, style))
79}
80
81pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
82    let keep_words = config.table.trim == TrimStrategy::wrap(true);
83    string_wrap(text, width, keep_words)
84}
85
86pub fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
87    TextStyle::with_style(
88        Alignment::Center,
89        style_computer.compute("header", &Value::string("", Span::unknown())),
90    )
91}
92
93pub fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
94    TextStyle::with_style(
95        Alignment::Right,
96        style_computer.compute("row_index", &Value::string("", Span::unknown())),
97    )
98}
99
100pub fn get_leading_trailing_space_style(style_computer: &StyleComputer) -> TextStyle {
101    TextStyle::with_style(
102        Alignment::Right,
103        style_computer.compute(
104            "leading_trailing_space_bg",
105            &Value::string("", Span::unknown()),
106        ),
107    )
108}
109
110pub fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
111    match value {
112        // Float precision is required here.
113        Value::Float { val, .. } => (
114            format!("{:.prec$}", val, prec = config.float_precision as usize),
115            style_computer.style_primitive(value),
116        ),
117        _ => (
118            value.to_abbreviated_string(config),
119            style_computer.style_primitive(value),
120        ),
121    }
122}
123
124pub fn get_empty_style(text: String, style_computer: &StyleComputer) -> NuText {
125    (
126        text,
127        TextStyle::with_style(
128            Alignment::Right,
129            style_computer.compute("empty", &Value::nothing(Span::unknown())),
130        ),
131    )
132}
133
134fn make_styled_value(
135    text: String,
136    value: &Value,
137    float_precision: usize,
138    style_computer: &StyleComputer,
139) -> NuText {
140    match value {
141        Value::Float { .. } => {
142            // set dynamic precision from config
143            let precise_number = match convert_with_precision(&text, float_precision) {
144                Ok(num) => num,
145                Err(e) => e.to_string(),
146            };
147
148            (precise_number, style_computer.style_primitive(value))
149        }
150        _ => (text, style_computer.style_primitive(value)),
151    }
152}
153
154fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
155    // vall will always be a f64 so convert it with precision formatting
156    let val_float = match val.trim().parse::<f64>() {
157        Ok(f) => f,
158        Err(e) => {
159            return Err(ShellError::GenericError {
160                error: format!("error converting string [{}] to f64", &val),
161                msg: "".into(),
162                span: None,
163                help: Some(e.to_string()),
164                inner: vec![],
165            });
166        }
167    };
168    Ok(format!("{val_float:.precision$}"))
169}
170
171pub fn load_theme(mode: TableMode) -> TableTheme {
172    match mode {
173        TableMode::Basic => TableTheme::basic(),
174        TableMode::Thin => TableTheme::thin(),
175        TableMode::Light => TableTheme::light(),
176        TableMode::Compact => TableTheme::compact(),
177        TableMode::WithLove => TableTheme::with_love(),
178        TableMode::CompactDouble => TableTheme::compact_double(),
179        TableMode::Rounded => TableTheme::rounded(),
180        TableMode::Reinforced => TableTheme::reinforced(),
181        TableMode::Heavy => TableTheme::heavy(),
182        TableMode::None => TableTheme::none(),
183        TableMode::Psql => TableTheme::psql(),
184        TableMode::Markdown => TableTheme::markdown(),
185        TableMode::Dots => TableTheme::dots(),
186        TableMode::Restructured => TableTheme::restructured(),
187        TableMode::AsciiRounded => TableTheme::ascii_rounded(),
188        TableMode::BasicCompact => TableTheme::basic_compact(),
189        TableMode::Single => TableTheme::single(),
190    }
191}
192
193fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
194    style_computer.compute("separator", &Value::nothing(Span::unknown()))
195}
196
197fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
198    with_header && need_footer(config, count_records as u64)
199}
200
201fn need_footer(config: &Config, count_records: u64) -> bool {
202    match config.footer_mode {
203        // Only show the footer if there are more than RowCount rows
204        FooterMode::RowCount(limit) => count_records > limit,
205        // Always show the footer
206        FooterMode::Always => true,
207        // Never show the footer
208        FooterMode::Never => false,
209        // Calculate the screen height and row count, if screen height is larger than row count, don't show footer
210        FooterMode::Auto => {
211            let (_width, height) = match terminal_size() {
212                Ok((w, h)) => (w as u64, h as u64),
213                _ => (0, 0),
214            };
215            height <= count_records
216        }
217    }
218}
219
220pub fn check_value(value: &Value) -> Result<(), ShellError> {
221    match value {
222        Value::Error { error, .. } => Err(*error.clone()),
223        _ => Ok(()),
224    }
225}