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
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(text: String, style_computer: &StyleComputer) -> (String, TextStyle) {
75 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 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 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 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 TableMode::Double => TableTheme::double(),
191 }
192}
193
194fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
195 style_computer.compute("separator", &Value::nothing(Span::unknown()))
196}
197
198fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
199 with_header && need_footer(config, count_records as u64)
200}
201
202fn need_footer(config: &Config, count_records: u64) -> bool {
203 match config.footer_mode {
204 FooterMode::RowCount(limit) => count_records > limit,
206 FooterMode::Always => true,
208 FooterMode::Never => false,
210 FooterMode::Auto => {
212 let (_width, height) = match terminal_size() {
213 Ok((w, h)) => (w as u64, h as u64),
214 _ => (0, 0),
215 };
216 height <= count_records
217 }
218 }
219}
220
221pub fn check_value(value: &Value) -> Result<(), ShellError> {
222 match value {
223 Value::Error { error, .. } => Err(*error.clone()),
224 _ => Ok(()),
225 }
226}