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

zhiburt / tabled / 11979956078

22 Nov 2024 08:50PM UTC coverage: 74.583% (-6.0%) from 80.545%
11979956078

push

github

zhiburt
tabled/ Bump to 0.17.0

12574 of 16859 relevant lines covered (74.58%)

2.55 hits per line

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

90.14
/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.
8
/// It's a combination of [`get_text_width`] and [`count_lines`].
9
#[cfg(feature = "std")]
10
pub fn get_text_dimension(text: &str) -> (usize, usize) {
2✔
11
    get_lines(text)
2✔
12
        .map(|line| get_line_width(&line))
4✔
13
        .fold((0, 0), |(i, acc), width| (i + 1, acc.max(width)))
4✔
14
}
15

16
/// Returns a string width.
17
pub fn get_line_width(text: &str) -> usize {
2✔
18
    #[cfg(not(feature = "ansi"))]
19
    {
20
        get_string_width(text)
2✔
21
    }
22

23
    #[cfg(feature = "ansi")]
24
    {
25
        // we need to strip ansi because of terminal links
26
        // and they're can't be stripped by ansi_str.
27

28
        ansitok::parse_ansi(text)
29
            .filter(|e| e.kind() == ansitok::ElementKind::Text)
30
            .map(|e| &text[e.start()..e.end()])
31
            .map(get_string_width)
32
            .sum()
33
    }
34
}
35

36
/// Returns a max string width of a line.
37
pub fn get_text_width(text: &str) -> usize {
2✔
38
    text.lines().map(get_line_width).max().unwrap_or(0)
2✔
39
}
40

41
/// Returns a char width.
42
pub fn get_char_width(c: char) -> usize {
×
43
    unicode_width::UnicodeWidthChar::width(c).unwrap_or_default()
×
44
}
45

46
/// Returns a string width (accouting all characters).
47
pub fn get_string_width(text: &str) -> usize {
2✔
48
    unicode_width::UnicodeWidthStr::width(text)
2✔
49
}
50

51
/// Calculates a number of lines.
52
pub fn count_lines(s: &str) -> usize {
2✔
53
    if s.is_empty() {
2✔
54
        return 1;
1✔
55
    }
56

57
    bytecount::count(s.as_bytes(), b'\n') + 1
4✔
58
}
59

60
/// Returns a list of tabs (`\t`) in a string..
61
pub fn count_tabs(s: &str) -> usize {
×
62
    bytecount::count(s.as_bytes(), b'\t')
×
63
}
64

65
/// Splits the string by lines.
66
#[cfg(feature = "std")]
67
pub fn get_lines(text: &str) -> Lines<'_> {
2✔
68
    #[cfg(not(feature = "ansi"))]
69
    {
70
        // we call `split()` but not `lines()` in order to match colored implementation
71
        // specifically how we treat a trailing '\n' character.
72
        Lines {
73
            inner: text.split('\n'),
2✔
74
        }
75
    }
76

77
    #[cfg(feature = "ansi")]
78
    {
79
        Lines {
80
            inner: ansi_str::AnsiStr::ansi_split(text, "\n"),
81
        }
82
    }
83
}
84

85
/// Iterator over lines.
86
///
87
/// In comparison to `std::str::Lines`, it treats trailing '\n' as a new line.
88
#[allow(missing_debug_implementations)]
89
#[cfg(feature = "std")]
90
pub struct Lines<'a> {
91
    #[cfg(not(feature = "ansi"))]
92
    inner: std::str::Split<'a, char>,
93
    #[cfg(feature = "ansi")]
94
    inner: ansi_str::AnsiSplit<'a>,
95
}
96
#[cfg(feature = "std")]
97
impl<'a> Iterator for Lines<'a> {
98
    type Item = std::borrow::Cow<'a, str>;
99

100
    fn next(&mut self) -> Option<Self::Item> {
2✔
101
        #[cfg(not(feature = "ansi"))]
102
        {
103
            self.inner.next().map(std::borrow::Cow::Borrowed)
2✔
104
        }
105

106
        #[cfg(feature = "ansi")]
107
        {
108
            self.inner.next()
×
109
        }
110
    }
111
}
112

113
#[cfg(feature = "std")]
114
/// Replaces tabs in a string with a given width of spaces.
115
pub fn replace_tab(text: &str, n: usize) -> std::borrow::Cow<'_, str> {
1✔
116
    if !text.contains('\t') {
1✔
117
        return std::borrow::Cow::Borrowed(text);
1✔
118
    }
119

120
    // it's a general case which probably must be faster?
121
    let replaced = if n == 4 {
2✔
122
        text.replace('\t', "    ")
×
123
    } else {
124
        let mut text = text.to_owned();
1✔
125
        replace_tab_range(&mut text, n);
1✔
126
        text
1✔
127
    };
128

129
    std::borrow::Cow::Owned(replaced)
1✔
130
}
131

132
#[cfg(feature = "std")]
133
fn replace_tab_range(cell: &mut String, n: usize) -> &str {
1✔
134
    let mut skip = 0;
1✔
135
    while let &Some(pos) = &cell[skip..].find('\t') {
1✔
136
        let pos = skip + pos;
2✔
137

138
        let is_escaped = pos > 0 && cell.get(pos - 1..pos) == Some("\\");
3✔
139
        if is_escaped {
1✔
140
            skip = pos + 1;
×
141
        } else if n == 0 {
3✔
142
            cell.remove(pos);
1✔
143
            skip = pos;
1✔
144
        } else {
145
            // I'am not sure which version is faster a loop of 'replace'
146
            // or allacation of a string for replacement;
147
            cell.replace_range(pos..=pos, &" ".repeat(n));
1✔
148
            skip = pos + 1;
1✔
149
        }
150

151
        if cell.is_empty() || skip >= cell.len() {
2✔
152
            break;
153
        }
154
    }
155

156
    cell
1✔
157
}
158

159
#[cfg(test)]
160
mod tests {
161
    use super::*;
162

163
    #[test]
164
    fn string_width_emojie_test() {
3✔
165
        // ...emojis such as “joy”, which normally take up two columns when printed in a terminal
166
        // https://github.com/mgeisler/textwrap/pull/276
167
        assert_eq!(get_line_width("🎩"), 2);
1✔
168
        assert_eq!(get_line_width("Rust 💕"), 7);
1✔
169
        assert_eq!(get_text_width("Go 👍\nC 😎"), 5);
1✔
170
    }
171

172
    #[cfg(feature = "ansi")]
173
    #[test]
174
    fn colored_string_width_test() {
175
        use owo_colors::OwoColorize;
176
        assert_eq!(get_line_width(&"hello world".red().to_string()), 11);
177
        assert_eq!(get_text_width(&"hello\nworld".blue().to_string()), 5);
178
        assert_eq!(get_line_width("\u{1b}[34m0\u{1b}[0m"), 1);
179
        assert_eq!(get_line_width(&"0".red().to_string()), 1);
180
    }
181

182
    #[test]
183
    fn count_lines_test() {
3✔
184
        assert_eq!(
1✔
185
            count_lines("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
186
            2
187
        );
188
        assert_eq!(count_lines("now is the time for all good men\n"), 2);
1✔
189
    }
190

191
    #[cfg(feature = "ansi")]
192
    #[test]
193
    fn string_width_multinline_for_link() {
194
        assert_eq!(
195
            get_text_width(
196
                "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
197
            ),
198
            7
199
        );
200
    }
201

202
    #[cfg(feature = "ansi")]
203
    #[test]
204
    fn string_width_for_link() {
205
        assert_eq!(
206
            get_line_width(
207
                "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
208
            ),
209
            7
210
        );
211
    }
212

213
    #[cfg(feature = "std")]
214
    #[test]
215
    fn string_dimension_test() {
3✔
216
        assert_eq!(
1✔
217
            get_text_dimension("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
218
            {
219
                #[cfg(feature = "ansi")]
220
                {
221
                    (2, 32)
222
                }
223
                #[cfg(not(feature = "ansi"))]
224
                {
225
                    (2, 37)
226
                }
227
            }
228
        );
229
        assert_eq!(
1✔
230
            get_text_dimension("now is the time for all good men\n"),
1✔
231
            (2, 32)
232
        );
233
        assert_eq!(get_text_dimension("asd"), (1, 3));
1✔
234
        assert_eq!(get_text_dimension(""), (1, 0));
1✔
235
    }
236

237
    #[cfg(feature = "std")]
238
    #[test]
239
    fn replace_tab_test() {
3✔
240
        assert_eq!(replace_tab("123\t\tabc\t", 3), "123      abc   ");
1✔
241

242
        assert_eq!(replace_tab("\t", 0), "");
1✔
243
        assert_eq!(replace_tab("\t", 3), "   ");
1✔
244
        assert_eq!(replace_tab("123\tabc", 3), "123   abc");
1✔
245
        assert_eq!(replace_tab("123\tabc\tzxc", 0), "123abczxc");
1✔
246

247
        assert_eq!(replace_tab("\\t", 0), "\\t");
1✔
248
        assert_eq!(replace_tab("\\t", 4), "\\t");
1✔
249
        assert_eq!(replace_tab("123\\tabc", 0), "123\\tabc");
1✔
250
        assert_eq!(replace_tab("123\\tabc", 4), "123\\tabc");
1✔
251
    }
252
}
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

© 2025 Coveralls, Inc