• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zhiburt / tabled / 10255982979

05 Aug 2024 09:01PM UTC coverage: 74.223% (-6.4%) from 80.669%
10255982979

push

github

zhiburt
Bump static_table to 0.4.0

12249 of 16503 relevant lines covered (74.22%)

2.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.84
/papergrid/src/util/string.rs
1
//! This module contains a different functions which are used by the [`Grid`].
2
//!
3
//! You should use it if you want to comply with how [`Grid`].
4
//!
5
//! [`Grid`]: crate::grid::iterable::Grid
6

7
/// Returns string width and count lines of a string. It's a combination of [`string_width_multiline`] and [`count_lines`].
8
#[cfg(feature = "std")]
9
pub fn get_text_dimension(text: &str) -> (usize, usize) {
2✔
10
    #[cfg(not(feature = "ansi"))]
11
    {
12
        let (lines, acc, max) = text.chars().fold((1, 0, 0), |(lines, acc, max), c| {
6✔
13
            if c == '\n' {
6✔
14
                (lines + 1, 0, acc.max(max))
4✔
15
            } else {
16
                let w = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
2✔
17
                (lines, acc + w, max)
4✔
18
            }
19
        });
20

21
        (lines, acc.max(max))
2✔
22
    }
23

24
    #[cfg(feature = "ansi")]
25
    {
26
        get_lines(text)
27
            .map(|line| get_line_width(&line))
28
            .fold((0, 0), |(i, acc), width| (i + 1, acc.max(width)))
29
    }
30
}
31

32
/// Returns a string width.
33
pub fn get_line_width(text: &str) -> usize {
2✔
34
    #[cfg(not(feature = "ansi"))]
35
    {
36
        unicode_width::UnicodeWidthStr::width(text)
2✔
37
    }
38

39
    #[cfg(feature = "ansi")]
40
    {
41
        // we need to strip ansi because of terminal links
42
        // and they're can't be stripped by ansi_str.
43

44
        ansitok::parse_ansi(text)
45
            .filter(|e| e.kind() == ansitok::ElementKind::Text)
46
            .map(|e| &text[e.start()..e.end()])
47
            .map(unicode_width::UnicodeWidthStr::width)
48
            .sum()
49
    }
50
}
51

52
/// Returns a max string width of a line.
53
pub fn get_text_width(text: &str) -> usize {
2✔
54
    #[cfg(not(feature = "ansi"))]
55
    {
56
        text.lines()
2✔
57
            .map(unicode_width::UnicodeWidthStr::width)
58
            .max()
59
            .unwrap_or(0)
60
    }
61

62
    #[cfg(feature = "ansi")]
63
    {
64
        text.lines().map(get_line_width).max().unwrap_or(0)
65
    }
66
}
67

68
/// Returns a char width.
69
pub fn get_char_width(c: char) -> usize {
×
70
    unicode_width::UnicodeWidthChar::width(c).unwrap_or_default()
×
71
}
72

73
/// Returns a string width (accouting all characters).
74
pub fn get_string_width(text: &str) -> usize {
×
75
    unicode_width::UnicodeWidthStr::width(text)
×
76
}
77

78
/// Calculates a number of lines.
79
pub fn count_lines(s: &str) -> usize {
2✔
80
    if s.is_empty() {
2✔
81
        return 1;
1✔
82
    }
83

84
    bytecount::count(s.as_bytes(), b'\n') + 1
4✔
85
}
86

87
/// Returns a list of tabs (`\t`) in a string..
88
pub fn count_tabs(s: &str) -> usize {
×
89
    bytecount::count(s.as_bytes(), b'\t')
×
90
}
91

92
/// Splits the string by lines.
93
#[cfg(feature = "std")]
94
pub fn get_lines(text: &str) -> Lines<'_> {
2✔
95
    #[cfg(not(feature = "ansi"))]
96
    {
97
        // we call `split()` but not `lines()` in order to match colored implementation
98
        // specifically how we treat a trailing '\n' character.
99
        Lines {
100
            inner: text.split('\n'),
2✔
101
        }
102
    }
103

104
    #[cfg(feature = "ansi")]
105
    {
106
        Lines {
107
            inner: ansi_str::AnsiStr::ansi_split(text, "\n"),
108
        }
109
    }
110
}
111

112
/// Iterator over lines.
113
///
114
/// In comparison to `std::str::Lines`, it treats trailing '\n' as a new line.
115
#[allow(missing_debug_implementations)]
116
#[cfg(feature = "std")]
117
pub struct Lines<'a> {
118
    #[cfg(not(feature = "ansi"))]
119
    inner: std::str::Split<'a, char>,
120
    #[cfg(feature = "ansi")]
121
    inner: ansi_str::AnsiSplit<'a>,
122
}
123
#[cfg(feature = "std")]
124
impl<'a> Iterator for Lines<'a> {
125
    type Item = std::borrow::Cow<'a, str>;
126

127
    fn next(&mut self) -> Option<Self::Item> {
2✔
128
        #[cfg(not(feature = "ansi"))]
129
        {
130
            self.inner.next().map(std::borrow::Cow::Borrowed)
2✔
131
        }
132

133
        #[cfg(feature = "ansi")]
134
        {
135
            self.inner.next()
×
136
        }
137
    }
138
}
139

140
#[cfg(feature = "std")]
141
/// Replaces tabs in a string with a given width of spaces.
142
pub fn replace_tab(text: &str, n: usize) -> std::borrow::Cow<'_, str> {
1✔
143
    if !text.contains('\t') {
1✔
144
        return std::borrow::Cow::Borrowed(text);
1✔
145
    }
146

147
    // it's a general case which probably must be faster?
148
    let replaced = if n == 4 {
2✔
149
        text.replace('\t', "    ")
×
150
    } else {
151
        let mut text = text.to_owned();
1✔
152
        replace_tab_range(&mut text, n);
1✔
153
        text
1✔
154
    };
155

156
    std::borrow::Cow::Owned(replaced)
1✔
157
}
158

159
#[cfg(feature = "std")]
160
fn replace_tab_range(cell: &mut String, n: usize) -> &str {
1✔
161
    let mut skip = 0;
1✔
162
    while let &Some(pos) = &cell[skip..].find('\t') {
1✔
163
        let pos = skip + pos;
2✔
164

165
        let is_escaped = pos > 0 && cell.get(pos - 1..pos) == Some("\\");
3✔
166
        if is_escaped {
1✔
167
            skip = pos + 1;
×
168
        } else if n == 0 {
3✔
169
            cell.remove(pos);
1✔
170
            skip = pos;
1✔
171
        } else {
172
            // I'am not sure which version is faster a loop of 'replace'
173
            // or allacation of a string for replacement;
174
            cell.replace_range(pos..=pos, &" ".repeat(n));
1✔
175
            skip = pos + 1;
1✔
176
        }
177

178
        if cell.is_empty() || skip >= cell.len() {
2✔
179
            break;
180
        }
181
    }
182

183
    cell
1✔
184
}
185

186
#[cfg(test)]
187
mod tests {
188
    use super::*;
189

190
    #[test]
191
    fn string_width_emojie_test() {
3✔
192
        // ...emojis such as “joy”, which normally take up two columns when printed in a terminal
193
        // https://github.com/mgeisler/textwrap/pull/276
194
        assert_eq!(get_line_width("🎩"), 2);
1✔
195
        assert_eq!(get_line_width("Rust 💕"), 7);
1✔
196
        assert_eq!(get_text_width("Go 👍\nC 😎"), 5);
1✔
197
    }
198

199
    #[cfg(feature = "ansi")]
200
    #[test]
201
    fn colored_string_width_test() {
202
        use owo_colors::OwoColorize;
203
        assert_eq!(get_line_width(&"hello world".red().to_string()), 11);
204
        assert_eq!(get_text_width(&"hello\nworld".blue().to_string()), 5);
205
        assert_eq!(get_line_width("\u{1b}[34m0\u{1b}[0m"), 1);
206
        assert_eq!(get_line_width(&"0".red().to_string()), 1);
207
    }
208

209
    #[test]
210
    fn count_lines_test() {
3✔
211
        assert_eq!(
1✔
212
            count_lines("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
213
            2
214
        );
215
        assert_eq!(count_lines("now is the time for all good men\n"), 2);
1✔
216
    }
217

218
    #[cfg(feature = "ansi")]
219
    #[test]
220
    fn string_width_multinline_for_link() {
221
        assert_eq!(
222
            get_text_width(
223
                "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
224
            ),
225
            7
226
        );
227
    }
228

229
    #[cfg(feature = "ansi")]
230
    #[test]
231
    fn string_width_for_link() {
232
        assert_eq!(
233
            get_line_width(
234
                "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
235
            ),
236
            7
237
        );
238
    }
239

240
    #[cfg(feature = "std")]
241
    #[test]
242
    fn string_dimension_test() {
3✔
243
        assert_eq!(
1✔
244
            get_text_dimension("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
245
            {
246
                #[cfg(feature = "ansi")]
247
                {
248
                    (2, 32)
249
                }
250
                #[cfg(not(feature = "ansi"))]
251
                {
252
                    (2, 36)
253
                }
254
            }
255
        );
256
        assert_eq!(
1✔
257
            get_text_dimension("now is the time for all good men\n"),
1✔
258
            (2, 32)
259
        );
260
        assert_eq!(get_text_dimension("asd"), (1, 3));
1✔
261
        assert_eq!(get_text_dimension(""), (1, 0));
1✔
262
    }
263

264
    #[cfg(feature = "std")]
265
    #[test]
266
    fn replace_tab_test() {
3✔
267
        assert_eq!(replace_tab("123\t\tabc\t", 3), "123      abc   ");
1✔
268

269
        assert_eq!(replace_tab("\t", 0), "");
1✔
270
        assert_eq!(replace_tab("\t", 3), "   ");
1✔
271
        assert_eq!(replace_tab("123\tabc", 3), "123   abc");
1✔
272
        assert_eq!(replace_tab("123\tabc\tzxc", 0), "123abczxc");
1✔
273

274
        assert_eq!(replace_tab("\\t", 0), "\\t");
1✔
275
        assert_eq!(replace_tab("\\t", 4), "\\t");
1✔
276
        assert_eq!(replace_tab("123\\tabc", 0), "123\\tabc");
1✔
277
        assert_eq!(replace_tab("123\\tabc", 4), "123\\tabc");
1✔
278
    }
279
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc