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

zhiburt / tabled / 11671238610

04 Nov 2024 07:25PM UTC coverage: 80.046% (+5.8%) from 74.214%
11671238610

push

github

web-flow
Merge pull request #434 from zhiburt/patch/refactorings-1

More refactorings

90 of 153 new or added lines in 21 files covered. (58.82%)

116 existing lines in 5 files now uncovered.

13515 of 16884 relevant lines covered (80.05%)

2.42 hits per line

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

93.06
/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) {
3✔
10
    get_lines(text)
3✔
11
        .map(|line| get_line_width(&line))
6✔
12
        .fold((0, 0), |(i, acc), width| (i + 1, acc.max(width)))
6✔
13
}
14

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

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

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

35
/// Returns a max string width of a line.
36
pub fn get_text_width(text: &str) -> usize {
4✔
37
    #[cfg(not(feature = "ansi"))]
38
    {
39
        text.lines().map(get_string_width).max().unwrap_or(0)
4✔
40
    }
41

42
    #[cfg(feature = "ansi")]
43
    {
44
        text.lines().map(get_line_width).max().unwrap_or(0)
45
    }
46
}
47

48
/// Returns a char width.
49
pub fn get_char_width(c: char) -> usize {
2✔
50
    unicode_width::UnicodeWidthChar::width(c).unwrap_or_default()
2✔
51
}
52

53
/// Returns a string width (accouting all characters).
54
pub fn get_string_width(text: &str) -> usize {
4✔
55
    #[cfg(feature = "std")]
56
    {
57
        let text = text.replace(|c| c < ' ', "");
12✔
58
        unicode_width::UnicodeWidthStr::width(text.as_str())
8✔
59
    }
60

61
    #[cfg(not(feature = "std"))]
62
    {
63
        // todo: make sure it's allright
64
        unicode_width::UnicodeWidthStr::width(text)
65
    }
66
}
67

68
/// Calculates a number of lines.
69
pub fn count_lines(s: &str) -> usize {
4✔
70
    if s.is_empty() {
4✔
71
        return 1;
2✔
72
    }
73

74
    bytecount::count(s.as_bytes(), b'\n') + 1
8✔
75
}
76

77
/// Returns a list of tabs (`\t`) in a string..
UNCOV
78
pub fn count_tabs(s: &str) -> usize {
×
79
    bytecount::count(s.as_bytes(), b'\t')
×
80
}
81

82
/// Splits the string by lines.
83
#[cfg(feature = "std")]
84
pub fn get_lines(text: &str) -> Lines<'_> {
3✔
85
    #[cfg(not(feature = "ansi"))]
86
    {
87
        // we call `split()` but not `lines()` in order to match colored implementation
88
        // specifically how we treat a trailing '\n' character.
89
        Lines {
90
            inner: text.split('\n'),
3✔
91
        }
92
    }
93

94
    #[cfg(feature = "ansi")]
95
    {
96
        Lines {
97
            inner: ansi_str::AnsiStr::ansi_split(text, "\n"),
98
        }
99
    }
100
}
101

102
/// Iterator over lines.
103
///
104
/// In comparison to `std::str::Lines`, it treats trailing '\n' as a new line.
105
#[allow(missing_debug_implementations)]
106
#[cfg(feature = "std")]
107
pub struct Lines<'a> {
108
    #[cfg(not(feature = "ansi"))]
109
    inner: std::str::Split<'a, char>,
110
    #[cfg(feature = "ansi")]
111
    inner: ansi_str::AnsiSplit<'a>,
112
}
113
#[cfg(feature = "std")]
114
impl<'a> Iterator for Lines<'a> {
115
    type Item = std::borrow::Cow<'a, str>;
116

117
    fn next(&mut self) -> Option<Self::Item> {
3✔
118
        #[cfg(not(feature = "ansi"))]
119
        {
120
            self.inner.next().map(std::borrow::Cow::Borrowed)
3✔
121
        }
122

123
        #[cfg(feature = "ansi")]
124
        {
UNCOV
125
            self.inner.next()
×
126
        }
127
    }
128
}
129

130
#[cfg(feature = "std")]
131
/// Replaces tabs in a string with a given width of spaces.
132
pub fn replace_tab(text: &str, n: usize) -> std::borrow::Cow<'_, str> {
1✔
133
    if !text.contains('\t') {
1✔
134
        return std::borrow::Cow::Borrowed(text);
1✔
135
    }
136

137
    // it's a general case which probably must be faster?
138
    let replaced = if n == 4 {
2✔
UNCOV
139
        text.replace('\t', "    ")
×
140
    } else {
141
        let mut text = text.to_owned();
1✔
142
        replace_tab_range(&mut text, n);
1✔
143
        text
1✔
144
    };
145

146
    std::borrow::Cow::Owned(replaced)
1✔
147
}
148

149
#[cfg(feature = "std")]
150
fn replace_tab_range(cell: &mut String, n: usize) -> &str {
1✔
151
    let mut skip = 0;
1✔
152
    while let &Some(pos) = &cell[skip..].find('\t') {
1✔
153
        let pos = skip + pos;
2✔
154

155
        let is_escaped = pos > 0 && cell.get(pos - 1..pos) == Some("\\");
3✔
156
        if is_escaped {
1✔
UNCOV
157
            skip = pos + 1;
×
158
        } else if n == 0 {
3✔
159
            cell.remove(pos);
1✔
160
            skip = pos;
1✔
161
        } else {
162
            // I'am not sure which version is faster a loop of 'replace'
163
            // or allacation of a string for replacement;
164
            cell.replace_range(pos..=pos, &" ".repeat(n));
1✔
165
            skip = pos + 1;
1✔
166
        }
167

168
        if cell.is_empty() || skip >= cell.len() {
2✔
169
            break;
170
        }
171
    }
172

173
    cell
1✔
174
}
175

176
#[cfg(test)]
177
mod tests {
178
    use super::*;
179

180
    #[test]
181
    fn string_width_emojie_test() {
3✔
182
        // ...emojis such as “joy”, which normally take up two columns when printed in a terminal
183
        // https://github.com/mgeisler/textwrap/pull/276
184
        assert_eq!(get_line_width("🎩"), 2);
1✔
185
        assert_eq!(get_line_width("Rust 💕"), 7);
1✔
186
        assert_eq!(get_text_width("Go 👍\nC 😎"), 5);
1✔
187
    }
188

189
    #[cfg(feature = "ansi")]
190
    #[test]
191
    fn colored_string_width_test() {
192
        use owo_colors::OwoColorize;
193
        assert_eq!(get_line_width(&"hello world".red().to_string()), 11);
194
        assert_eq!(get_text_width(&"hello\nworld".blue().to_string()), 5);
195
        assert_eq!(get_line_width("\u{1b}[34m0\u{1b}[0m"), 1);
196
        assert_eq!(get_line_width(&"0".red().to_string()), 1);
197
    }
198

199
    #[test]
200
    fn count_lines_test() {
3✔
201
        assert_eq!(
1✔
202
            count_lines("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
203
            2
204
        );
205
        assert_eq!(count_lines("now is the time for all good men\n"), 2);
1✔
206
    }
207

208
    #[cfg(feature = "ansi")]
209
    #[test]
210
    fn string_width_multinline_for_link() {
211
        assert_eq!(
212
            get_text_width(
213
                "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"
214
            ),
215
            7
216
        );
217
    }
218

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

230
    #[cfg(feature = "std")]
231
    #[test]
232
    fn string_dimension_test() {
3✔
233
        assert_eq!(
1✔
234
            get_text_dimension("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"),
1✔
235
            {
236
                #[cfg(feature = "ansi")]
237
                {
238
                    (2, 32)
239
                }
240
                #[cfg(not(feature = "ansi"))]
241
                {
242
                    (2, 36)
243
                }
244
            }
245
        );
246
        assert_eq!(
1✔
247
            get_text_dimension("now is the time for all good men\n"),
1✔
248
            (2, 32)
249
        );
250
        assert_eq!(get_text_dimension("asd"), (1, 3));
1✔
251
        assert_eq!(get_text_dimension(""), (1, 0));
1✔
252
    }
253

254
    #[cfg(feature = "std")]
255
    #[test]
256
    fn replace_tab_test() {
3✔
257
        assert_eq!(replace_tab("123\t\tabc\t", 3), "123      abc   ");
1✔
258

259
        assert_eq!(replace_tab("\t", 0), "");
1✔
260
        assert_eq!(replace_tab("\t", 3), "   ");
1✔
261
        assert_eq!(replace_tab("123\tabc", 3), "123   abc");
1✔
262
        assert_eq!(replace_tab("123\tabc\tzxc", 0), "123abczxc");
1✔
263

264
        assert_eq!(replace_tab("\\t", 0), "\\t");
1✔
265
        assert_eq!(replace_tab("\\t", 4), "\\t");
1✔
266
        assert_eq!(replace_tab("123\\tabc", 0), "123\\tabc");
1✔
267
        assert_eq!(replace_tab("123\\tabc", 4), "123\\tabc");
1✔
268
    }
269
}
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