nu_table/
util.rs

1use nu_color_config::StyleComputer;
2
3use tabled::{
4    grid::{
5        ansi::{ANSIBuf, ANSIStr},
6        records::vec_records::Text,
7        util::string::get_text_width,
8    },
9    settings::{
10        width::{Truncate, Wrap},
11        Color,
12    },
13};
14
15use crate::common::get_leading_trailing_space_style;
16
17pub fn string_width(text: &str) -> usize {
18    get_text_width(text)
19}
20
21pub fn string_wrap(text: &str, width: usize, keep_words: bool) -> String {
22    if text.is_empty() {
23        return String::new();
24    }
25
26    let text_width = string_width(text);
27    if text_width <= width {
28        return text.to_owned();
29    }
30
31    Wrap::wrap(text, width, keep_words)
32}
33
34pub fn string_expand(text: &str, width: usize) -> String {
35    use std::{borrow::Cow, iter::repeat_n};
36    use tabled::grid::util::string::{get_line_width, get_lines};
37
38    get_lines(text)
39        .map(|line| {
40            let length = get_line_width(&line);
41
42            if length < width {
43                let mut line = line.into_owned();
44                let remain = width - length;
45                line.extend(repeat_n(' ', remain));
46                Cow::Owned(line)
47            } else {
48                line
49            }
50        })
51        .collect::<Vec<_>>()
52        .join("\n")
53}
54
55pub fn string_truncate(text: &str, width: usize) -> String {
56    let line = match text.lines().next() {
57        Some(line) => line,
58        None => return String::new(),
59    };
60
61    Truncate::truncate(line, width).into_owned()
62}
63
64pub fn clean_charset(text: &str) -> String {
65    // TODO: We could make an optimization to take a String and modify it
66    //       We could check if there was any changes and if not make no allocations at all and don't change the origin.
67    //       Why it's not done...
68    //       Cause I am not sure how the `if` in a loop will affect performance.
69    //       So it's better be profiled, but likely the optimization be worth it.
70    //       At least because it's a base case where we won't change anything....
71
72    // allocating at least the text size,
73    // in most cases the buf will be a copy of text anyhow.
74    //
75    // but yes sometimes we will alloc more then necessary.
76    // We could shrink it but...it will be another realloc which make no scense.
77    let mut buf = String::with_capacity(text.len());
78
79    // note: (Left just in case)
80    // note: This check could be added in order to cope with emojie issue.
81    // if c < ' ' && c != '\u{1b}' {
82    //    continue;
83    // }
84
85    for c in text.chars() {
86        match c {
87            '\r' => continue,
88            '\t' => {
89                buf.push(' ');
90                buf.push(' ');
91                buf.push(' ');
92                buf.push(' ');
93            }
94            c => {
95                buf.push(c);
96            }
97        }
98    }
99
100    buf
101}
102
103pub fn colorize_space(data: &mut [Vec<Text<String>>], style_computer: &StyleComputer<'_>) {
104    let style = match get_leading_trailing_space_style(style_computer).color_style {
105        Some(color) => color,
106        None => return,
107    };
108
109    let style = ANSIBuf::from(convert_style(style));
110    let style = style.as_ref();
111    if style.is_empty() {
112        return;
113    }
114
115    colorize_list(data, style, style);
116}
117
118pub fn colorize_space_str(text: &mut String, style_computer: &StyleComputer<'_>) {
119    let style = match get_leading_trailing_space_style(style_computer).color_style {
120        Some(color) => color,
121        None => return,
122    };
123
124    let style = ANSIBuf::from(convert_style(style));
125    let style = style.as_ref();
126    if style.is_empty() {
127        return;
128    }
129
130    *text = colorize_space_one(text, style, style);
131}
132
133fn colorize_list(data: &mut [Vec<Text<String>>], lead: ANSIStr<'_>, trail: ANSIStr<'_>) {
134    for row in data.iter_mut() {
135        for cell in row {
136            let buf = colorize_space_one(cell.as_ref(), lead, trail);
137            *cell = Text::new(buf);
138        }
139    }
140}
141
142fn colorize_space_one(text: &str, lead: ANSIStr<'_>, trail: ANSIStr<'_>) -> String {
143    use fancy_regex::Captures;
144    use fancy_regex::Regex;
145    use std::sync::LazyLock;
146
147    static RE_LEADING: LazyLock<Regex> = LazyLock::new(|| {
148        Regex::new(r"(?m)(?P<beginsp>^\s+)").expect("error with leading space regex")
149    });
150    static RE_TRAILING: LazyLock<Regex> = LazyLock::new(|| {
151        Regex::new(r"(?m)(?P<endsp>\s+$)").expect("error with trailing space regex")
152    });
153
154    let mut buf = text.to_owned();
155
156    if !lead.is_empty() {
157        buf = RE_LEADING
158            .replace_all(&buf, |cap: &Captures| {
159                let spaces = cap.get(1).expect("valid").as_str();
160                format!("{}{}{}", lead.get_prefix(), spaces, lead.get_suffix())
161            })
162            .into_owned();
163    }
164
165    if !trail.is_empty() {
166        buf = RE_TRAILING
167            .replace_all(&buf, |cap: &Captures| {
168                let spaces = cap.get(1).expect("valid").as_str();
169                format!("{}{}{}", trail.get_prefix(), spaces, trail.get_suffix())
170            })
171            .into_owned();
172    }
173
174    buf
175}
176
177pub fn convert_style(style: nu_ansi_term::Style) -> Color {
178    Color::new(style.prefix().to_string(), style.suffix().to_string())
179}
180
181pub fn is_color_empty(c: &Color) -> bool {
182    c.get_prefix().is_empty() && c.get_suffix().is_empty()
183}