1use crate::{clean_charset, colorize_space_str, string_wrap, TableOutput, TableTheme};
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
64pub 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(style_computer: &StyleComputer) -> (String, TextStyle) {
75 let text = String::from("❎");
78 let style = style_computer.compute("empty", &Value::nothing(Span::unknown()));
79 (text, TextStyle::with_style(Alignment::Center, style))
80}
81
82pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
83 let keep_words = config.table.trim == TrimStrategy::wrap(true);
84 string_wrap(text, width, keep_words)
85}
86
87pub fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
88 TextStyle::with_style(
89 Alignment::Center,
90 style_computer.compute("header", &Value::string("", Span::unknown())),
91 )
92}
93
94pub fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
95 TextStyle::with_style(
96 Alignment::Right,
97 style_computer.compute("row_index", &Value::string("", Span::unknown())),
98 )
99}
100
101pub fn get_leading_trailing_space_style(style_computer: &StyleComputer) -> TextStyle {
102 TextStyle::with_style(
103 Alignment::Right,
104 style_computer.compute(
105 "leading_trailing_space_bg",
106 &Value::string("", Span::unknown()),
107 ),
108 )
109}
110
111pub fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
112 match value {
113 Value::Float { val, .. } => (
115 format!("{:.prec$}", val, prec = config.float_precision as usize),
116 style_computer.style_primitive(value),
117 ),
118 _ => (
119 value.to_abbreviated_string(config),
120 style_computer.style_primitive(value),
121 ),
122 }
123}
124
125pub fn get_empty_style(style_computer: &StyleComputer) -> NuText {
126 (
127 String::from("❎"),
128 TextStyle::with_style(
129 Alignment::Right,
130 style_computer.compute("empty", &Value::nothing(Span::unknown())),
131 ),
132 )
133}
134
135fn make_styled_value(
136 text: String,
137 value: &Value,
138 float_precision: usize,
139 style_computer: &StyleComputer,
140) -> NuText {
141 match value {
142 Value::Float { .. } => {
143 let precise_number = match convert_with_precision(&text, float_precision) {
145 Ok(num) => num,
146 Err(e) => e.to_string(),
147 };
148
149 (precise_number, style_computer.style_primitive(value))
150 }
151 _ => (text, style_computer.style_primitive(value)),
152 }
153}
154
155fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
156 let val_float = match val.trim().parse::<f64>() {
158 Ok(f) => f,
159 Err(e) => {
160 return Err(ShellError::GenericError {
161 error: format!("error converting string [{}] to f64", &val),
162 msg: "".into(),
163 span: None,
164 help: Some(e.to_string()),
165 inner: vec![],
166 });
167 }
168 };
169 Ok(format!("{val_float:.precision$}"))
170}
171
172pub fn load_theme(mode: TableMode) -> TableTheme {
173 match mode {
174 TableMode::Basic => TableTheme::basic(),
175 TableMode::Thin => TableTheme::thin(),
176 TableMode::Light => TableTheme::light(),
177 TableMode::Compact => TableTheme::compact(),
178 TableMode::WithLove => TableTheme::with_love(),
179 TableMode::CompactDouble => TableTheme::compact_double(),
180 TableMode::Rounded => TableTheme::rounded(),
181 TableMode::Reinforced => TableTheme::reinforced(),
182 TableMode::Heavy => TableTheme::heavy(),
183 TableMode::None => TableTheme::none(),
184 TableMode::Psql => TableTheme::psql(),
185 TableMode::Markdown => TableTheme::markdown(),
186 TableMode::Dots => TableTheme::dots(),
187 TableMode::Restructured => TableTheme::restructured(),
188 TableMode::AsciiRounded => TableTheme::ascii_rounded(),
189 TableMode::BasicCompact => TableTheme::basic_compact(),
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 FooterMode::RowCount(limit) => count_records > limit,
205 FooterMode::Always => true,
207 FooterMode::Never => false,
209 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}