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 Color,
11 width::{Truncate, Wrap},
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 let mut buf = String::with_capacity(text.len());
78
79 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}