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