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

zhiburt / tabled / 11882447959

17 Nov 2024 09:42PM UTC coverage: 80.227% (+0.1%) from 80.128%
11882447959

push

github

web-flow
Merge pull request #450 from zhiburt/patch/fix-clippy1

Address Charset changes and Wrap improvement

19 of 21 new or added lines in 5 files covered. (90.48%)

53 existing lines in 3 files now uncovered.

13742 of 17129 relevant lines covered (80.23%)

2.43 hits per line

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

96.83
/tabled/src/util/string.rs
1
use std::borrow::Cow;
2

3
use crate::grid::util::string::get_char_width;
4

5
/// The function cuts the string to a specific width.
6
/// Preserving colors with `ansi` feature on.
7
pub(crate) fn split_str(s: &str, width: usize) -> (Cow<'_, str>, Cow<'_, str>) {
2✔
8
    #[cfg(feature = "ansi")]
9
    {
10
        const REPLACEMENT: char = '\u{FFFD}';
11

12
        let stripped = ansi_str::AnsiStr::ansi_strip(s);
13
        let (length, cutwidth, csize) = split_at_width(&stripped, width);
14
        let (mut lhs, mut rhs) = ansi_str::AnsiStr::ansi_split_at(s, length);
15

16
        if csize > 0 {
17
            let mut buf = lhs.into_owned();
18
            let count_unknowns = width - cutwidth;
19
            buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
20
            lhs = Cow::Owned(buf);
21
            rhs = Cow::Owned(ansi_str::AnsiStr::ansi_cut(rhs.as_ref(), csize..).into_owned());
22
        }
23

24
        (lhs, rhs)
25
    }
26

27
    #[cfg(not(feature = "ansi"))]
28
    {
29
        const REPLACEMENT: char = '\u{FFFD}';
30

31
        let (length, cutwidth, csize) = split_at_width(s, width);
2✔
32
        let (lhs, rhs) = s.split_at(length);
2✔
33

34
        if csize == 0 {
2✔
35
            return (Cow::Borrowed(lhs), Cow::Borrowed(rhs));
2✔
36
        }
37

38
        let count_unknowns = width - cutwidth;
2✔
39
        let mut buf = lhs.to_owned();
1✔
40
        buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
2✔
41

42
        (Cow::Owned(buf), Cow::Borrowed(&rhs[csize..]))
1✔
43
    }
44
}
45

46
/// The function cuts the string to a specific width.
47
/// Preserving colors with `ansi` feature on.
48
pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> {
2✔
49
    #[cfg(feature = "ansi")]
50
    {
51
        const REPLACEMENT: char = '\u{FFFD}';
52

53
        let stripped = ansi_str::AnsiStr::ansi_strip(s);
54
        let (length, cutwidth, csize) = split_at_width(&stripped, width);
55
        let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length);
56
        if csize != 0 {
57
            let mut b = buf.into_owned();
58
            let count_unknowns = width - cutwidth;
59
            b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
60
            buf = Cow::Owned(b);
61
        }
62

63
        buf
64
    }
65

66
    #[cfg(not(feature = "ansi"))]
67
    {
68
        cut_str2(s, width)
2✔
69
    }
70
}
71

72
/// The function cuts the string to a specific width.
73
/// While not preserving ansi sequences.
74
pub(crate) fn cut_str2(text: &str, width: usize) -> Cow<'_, str> {
2✔
75
    const REPLACEMENT: char = '\u{FFFD}';
76

77
    let (length, cutwidth, csize) = split_at_width(text, width);
2✔
78
    if csize == 0 {
2✔
79
        let buf = &text[..length];
2✔
80
        return Cow::Borrowed(buf);
2✔
81
    }
82

83
    let buf = &text[..length];
2✔
84
    let mut buf = buf.to_owned();
2✔
85
    let count_unknowns = width - cutwidth;
4✔
86
    buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
4✔
87

88
    Cow::Owned(buf)
2✔
89
}
90

91
/// The function splits a string in the position and
92
/// returns a exact number of bytes before the position and in case of a split in an unicode grapheme
93
/// a width of a character which was tried to be split in.
94
pub(crate) fn split_at_width(s: &str, at_width: usize) -> (usize, usize, usize) {
2✔
95
    let mut length = 0;
2✔
96
    let mut width = 0;
2✔
97
    for c in s.chars() {
6✔
98
        if width == at_width {
2✔
99
            break;
100
        };
101

102
        if c == '\n' {
2✔
NEW
103
            width = 0;
×
NEW
104
            length += 1;
×
105
            continue;
106
        }
107

108
        let c_width = get_char_width(c);
2✔
109
        let c_length = c.len_utf8();
2✔
110

111
        // We cut the chars which takes more then 1 symbol to display,
112
        // in order to archive the necessary width.
113
        if width + c_width > at_width {
4✔
114
            return (length, width, c_length);
2✔
115
        }
116

117
        width += c_width;
4✔
118
        length += c_length;
4✔
119
    }
120

121
    (length, width, 0)
2✔
122
}
123

124
/// Strip OSC codes from `s`. If `s` is a single OSC8 hyperlink, with no other text, then return
125
/// (s_with_all_hyperlinks_removed, Some(url)). If `s` does not meet this description, then return
126
/// (s_with_all_hyperlinks_removed, None). Any ANSI color sequences in `s` will be retained. See
127
/// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>
128
///
129
/// The function is based on Dan Davison <https://github.com/dandavison> delta <https://github.com/dandavison/delta> ansi library.
130
#[cfg(feature = "ansi")]
131
pub(crate) fn strip_osc(text: &str) -> (String, Option<String>) {
132
    #[derive(Debug)]
133
    enum ExtractOsc8HyperlinkState {
134
        ExpectOsc8Url,
135
        ExpectFirstText,
136
        ExpectMoreTextOrTerminator,
137
        SeenOneHyperlink,
138
        WillNotReturnUrl,
139
    }
140

141
    use ExtractOsc8HyperlinkState::*;
142

143
    let mut url = None;
144
    let mut state = ExpectOsc8Url;
145
    let mut buf = String::with_capacity(text.len());
146

147
    for el in ansitok::parse_ansi(text) {
148
        match el.kind() {
149
            ansitok::ElementKind::Osc => match state {
150
                ExpectOsc8Url => {
151
                    url = Some(&text[el.start()..el.end()]);
152
                    state = ExpectFirstText;
153
                }
154
                ExpectMoreTextOrTerminator => state = SeenOneHyperlink,
155
                _ => state = WillNotReturnUrl,
156
            },
157
            ansitok::ElementKind::Sgr => buf.push_str(&text[el.start()..el.end()]),
158
            ansitok::ElementKind::Csi => buf.push_str(&text[el.start()..el.end()]),
159
            ansitok::ElementKind::Esc => {}
160
            ansitok::ElementKind::Text => {
161
                buf.push_str(&text[el.start()..el.end()]);
162
                match state {
163
                    ExpectFirstText => state = ExpectMoreTextOrTerminator,
164
                    ExpectMoreTextOrTerminator => {}
165
                    _ => state = WillNotReturnUrl,
166
                }
167
            }
168
        }
169
    }
170

171
    match state {
172
        WillNotReturnUrl => (buf, None),
173
        _ => {
174
            let url = url.and_then(|s| {
175
                s.strip_prefix("\x1b]8;;")
176
                    .and_then(|s| s.strip_suffix('\x1b'))
177
            });
178
            if let Some(url) = url {
179
                (buf, Some(url.to_string()))
180
            } else {
181
                (buf, None)
182
            }
183
        }
184
    }
185
}
186

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

191
    use crate::grid::util::string::get_line_width;
192

193
    #[cfg(feature = "ansi")]
194
    use owo_colors::{colors::Yellow, OwoColorize};
195

196
    #[test]
197
    fn strip_test() {
3✔
198
        assert_eq!(cut_str("123456", 0), "");
1✔
199
        assert_eq!(cut_str("123456", 3), "123");
1✔
200
        assert_eq!(cut_str("123456", 10), "123456");
1✔
201

202
        assert_eq!(cut_str("a week ago", 4), "a we");
1✔
203

204
        assert_eq!(cut_str("😳😳😳😳😳", 0), "");
1✔
205
        assert_eq!(cut_str("😳😳😳😳😳", 3), "😳�");
1✔
206
        assert_eq!(cut_str("😳😳😳😳😳", 4), "😳😳");
1✔
207
        assert_eq!(cut_str("😳😳😳😳😳", 20), "😳😳😳😳😳");
1✔
208

209
        assert_eq!(cut_str("🏳️🏳️", 0), "");
1✔
210
        assert_eq!(cut_str("🏳️🏳️", 1), "🏳");
1✔
211
        assert_eq!(cut_str("🏳️🏳️", 2), "🏳\u{fe0f}🏳");
1✔
212
        assert_eq!(get_line_width("🏳️🏳️"), get_line_width("🏳\u{fe0f}🏳\u{fe0f}"));
1✔
213

214
        assert_eq!(cut_str("🎓", 1), "�");
1✔
215
        assert_eq!(cut_str("🎓", 2), "🎓");
1✔
216

217
        assert_eq!(cut_str("🥿", 1), "�");
1✔
218
        assert_eq!(cut_str("🥿", 2), "🥿");
1✔
219

220
        assert_eq!(cut_str("🩰", 1), "�");
1✔
221
        assert_eq!(cut_str("🩰", 2), "🩰");
1✔
222

223
        assert_eq!(cut_str("👍🏿", 1), "�");
1✔
224
        assert_eq!(cut_str("👍🏿", 2), "👍");
1✔
225
        assert_eq!(cut_str("👍🏿", 3), "👍�");
1✔
226
        assert_eq!(cut_str("👍🏿", 4), "👍🏿");
1✔
227

228
        assert_eq!(cut_str("🇻🇬", 1), "🇻");
1✔
229
        assert_eq!(cut_str("🇻🇬", 2), "🇻🇬");
1✔
230
        assert_eq!(cut_str("🇻🇬", 3), "🇻🇬");
1✔
231
        assert_eq!(cut_str("🇻🇬", 4), "🇻🇬");
1✔
232
    }
233

234
    #[cfg(feature = "ansi")]
235
    #[test]
236
    fn strip_color_test() {
237
        let numbers = "123456".red().on_bright_black().to_string();
238

239
        assert_eq!(cut_str(&numbers, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
240
        assert_eq!(
241
            cut_str(&numbers, 3),
242
            "\u{1b}[31;100m123\u{1b}[39m\u{1b}[49m"
243
        );
244
        assert_eq!(cut_str(&numbers, 10), "\u{1b}[31;100m123456\u{1b}[0m");
245

246
        let emojies = "😳😳😳😳😳".red().on_bright_black().to_string();
247

248
        assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
249
        assert_eq!(
250
            cut_str(&emojies, 3),
251
            "\u{1b}[31;100m😳\u{1b}[39m\u{1b}[49m�"
252
        );
253
        assert_eq!(
254
            cut_str(&emojies, 4),
255
            "\u{1b}[31;100m😳😳\u{1b}[39m\u{1b}[49m"
256
        );
257
        assert_eq!(cut_str(&emojies, 20), "\u{1b}[31;100m😳😳😳😳😳\u{1b}[0m");
258

259
        let emojies = "🏳️🏳️".red().on_bright_black().to_string();
260

261
        assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m");
262
        assert_eq!(cut_str(&emojies, 1), "\u{1b}[31;100m🏳\u{1b}[39m\u{1b}[49m");
263
        assert_eq!(
264
            cut_str(&emojies, 2),
265
            "\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m"
266
        );
267
        assert_eq!(
268
            get_line_width(&emojies),
269
            get_line_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{fe0f}\u{1b}[39m\u{1b}[49m")
270
        );
271
    }
272

273
    #[test]
274
    #[cfg(feature = "ansi")]
275
    fn test_color_strip() {
276
        let s = "Collored string"
277
            .fg::<Yellow>()
278
            .on_truecolor(12, 200, 100)
279
            .blink()
280
            .to_string();
281
        assert_eq!(
282
            cut_str(&s, 1),
283
            "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m"
284
        )
285
    }
286

287
    #[test]
288
    #[cfg(feature = "ansi")]
289
    fn test_srip_osc() {
290
        assert_eq!(
291
            strip_osc("just a string here"),
292
            (String::from("just a string here"), None)
293
        );
294
        assert_eq!(
295
            strip_osc("/etc/rc.conf"),
296
            (String::from("/etc/rc.conf"), None)
297
        );
298
        assert_eq!(
299
            strip_osc(
300
                "https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982"
301
            ),
302
            (String::from("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982"), None)
303
        );
304

305
        assert_eq!(
306
            strip_osc(&build_link_prefix_suffix("just a string here")),
307
            (String::default(), Some(String::from("just a string here")))
308
        );
309
        assert_eq!(
310
            strip_osc(&build_link_prefix_suffix("/etc/rc.conf")),
311
            (String::default(), Some(String::from("/etc/rc.conf")))
312
        );
313
        assert_eq!(
314
            strip_osc(
315
                &build_link_prefix_suffix("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982")
316
            ),
317
            (String::default(), Some(String::from("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982")))
318
        );
319

320
        #[cfg(feature = "ansi")]
321
        fn build_link_prefix_suffix(url: &str) -> String {
322
            // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
323
            let osc8 = "\x1b]8;;";
324
            let st = "\x1b\\";
325
            format!("{osc8}{url}{st}")
326
        }
327
    }
328
}
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