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

veeso / tui-realm-stdlib / 22147789116

09 Feb 2026 04:20PM UTC coverage: 77.303% (+4.8%) from 72.482%
22147789116

push

github

hasezoey
refactor(table): switch to use "CommonProps"

18 of 90 new or added lines in 1 file covered. (20.0%)

405 existing lines in 20 files now uncovered.

3944 of 5102 relevant lines covered (77.3%)

4.92 hits per line

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

95.83
/src/utils.rs
1
//! ## Utils
2
//!
3
//! Utilities functions to work with components
4

5
use std::borrow::Cow;
6

7
use tuirealm::props::{Borders, Title};
8
use tuirealm::ratatui::style::{Color, Style};
9
use tuirealm::ratatui::text::{Line, Span, Text};
10
use tuirealm::ratatui::widgets::{Block, TitlePosition};
11
use unicode_width::UnicodeWidthStr;
12

13
/// ### wrap_spans
14
///
15
/// Given a vector of [`Span`]s, it creates a list of `Spans` which mustn't exceed the provided width parameter.
16
/// Each [`Line`] in the returned `Vec` is a line in the text.
17
#[must_use]
18
pub fn wrap_spans<'a, 'b: 'a>(spans: &[&'b Span<'a>], width: usize) -> Vec<Line<'a>> {
10✔
19
    // Prepare result (capacity will be at least spans.len)
5✔
20
    let mut res: Vec<Line> = Vec::with_capacity(spans.len());
10✔
21
    // Prepare environment
5✔
22
    let mut line_width: usize = 0; // Incremental line width; mustn't exceed `width`.
10✔
23
    let mut line_spans: Vec<Span> = Vec::new(); // Current line; when done, push to res and re-initialize
10✔
24
    for span in spans {
27✔
25
        // Check if width would exceed...
26
        if line_width + span.content.width() > width {
22✔
27
            // Check if entire line is wider than the area
28
            if span.content.width() > width {
14✔
29
                // Wrap
30
                let span_lines = textwrap::wrap(&span.content, width);
8✔
31
                // iter lines
32
                for span_line in span_lines {
24✔
33
                    // Check if width would exceed...
34
                    if line_width + span_line.width() > width {
20✔
35
                        // New line
12✔
36
                        res.push(Line::from(line_spans));
12✔
37
                        line_width = 0;
12✔
38
                        line_spans = Vec::new();
12✔
39
                    }
12✔
40
                    // Increment line width
41
                    line_width += span_line.width();
20✔
42
                    // Push to line
10✔
43
                    line_spans.push(Span::styled(span_line, span.style));
20✔
44
                }
45
                // Go to next iteration
46
                continue;
8✔
47
            }
6✔
48
            // Just initialize a new line
3✔
49
            res.push(Line::from(line_spans));
6✔
50
            line_width = 0;
6✔
51
            line_spans = Vec::new();
6✔
52
        }
8✔
53
        // Push span to line
54
        line_width += span.content.width();
14✔
55
        line_spans.push(Span::styled(span.content.to_string(), span.style));
14✔
56
    }
57
    // if there are still elements in spans, push to result
58
    if !line_spans.is_empty() {
10✔
59
        res.push(Line::from(line_spans));
10✔
60
    }
10✔
61
    // return res
62
    res
10✔
63
}
10✔
64

65
/// ### get_block
66
///
67
/// Construct a block for widget using block properties.
68
/// If focus is true the border color is applied, otherwise inactive_style
69
#[must_use]
70
pub fn get_block(
6✔
71
    props: Borders,
6✔
72
    title: Option<&Title>,
6✔
73
    focus: bool,
6✔
74
    inactive_style: Option<Style>,
6✔
75
) -> Block<'_> {
6✔
76
    let mut block = Block::default()
6✔
77
        .borders(props.sides)
6✔
78
        .border_style(if focus {
6✔
79
            props.style()
2✔
80
        } else {
81
            inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
4✔
82
        })
83
        .border_type(props.modifiers);
6✔
84

85
    if let Some(title) = title {
6✔
86
        block = match title.position {
4✔
87
            TitlePosition::Top => block.title_top(borrow_clone_line(&title.content)),
2✔
88
            TitlePosition::Bottom => block.title_bottom(borrow_clone_line(&title.content)),
2✔
89
        };
90
    }
2✔
91

92
    block
6✔
93
}
6✔
94

95
/// ### calc_utf8_cursor_position
96
///
97
/// Calculate the UTF8 compliant position for the cursor given the characters preceeding the cursor position.
98
/// Use this function to calculate cursor position whenever you want to handle UTF8 texts with cursors
99
#[must_use]
100
pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
10✔
101
    chars.iter().collect::<String>().width() as u16
10✔
102
}
10✔
103

104
/// Convert a `&Span` to a `Span` by using [`Cow::Borrowed`].
105
///
106
/// Note that a normal `Span::clone` (and by extension `Cow::clone`) will preserve the `Cow` Variant.
107
pub fn borrow_clone_span<'a, 'b: 'a>(span: &'b Span<'a>) -> Span<'a> {
4✔
108
    Span {
4✔
109
        style: span.style,
4✔
110
        content: Cow::Borrowed(&*span.content),
4✔
111
    }
4✔
112
}
4✔
113

114
/// Convert a `&Line` to a `Line` by using [`Cow::Borrowed`].
115
pub fn borrow_clone_line<'a, 'b: 'a>(line: &'b Line<'a>) -> Line<'a> {
4✔
116
    Line {
4✔
117
        spans: line.spans.iter().map(borrow_clone_span).collect(),
4✔
118
        ..*line
4✔
119
    }
4✔
120
}
4✔
121

122
/// Convert a `&Text` to a `Text` by using [`Cow::Borrowed`].
UNCOV
123
pub fn borrow_clone_text<'a, 'b: 'a>(text: &'b Text<'a>) -> Text<'a> {
×
UNCOV
124
    Text {
×
UNCOV
125
        lines: text.lines.iter().map(borrow_clone_line).collect(),
×
UNCOV
126
        ..*text
×
UNCOV
127
    }
×
128
}
×
129

130
#[cfg(test)]
131
mod test {
132

133
    use super::*;
134
    use tuirealm::props::{BorderSides, BorderType, HorizontalAlignment};
135

136
    use pretty_assertions::assert_eq;
137

138
    #[test]
139
    fn test_components_utils_wrap_spans() {
2✔
140
        // Prepare spans; let's start with two simple spans, which fits the line
1✔
141
        let spans: Vec<Span> = vec![Span::from("hello, "), Span::from("world!")];
2✔
142
        let spans: Vec<&Span> = spans.iter().collect();
2✔
143
        assert_eq!(wrap_spans(&spans, 64).len(), 1);
2✔
144
        // Let's make a sentence, which would require two lines
145
        let spans: Vec<Span> = vec![
2✔
146
            Span::from("Hello, everybody, I'm Uncle Camel!"),
2✔
147
            Span::from("How's it going today?"),
2✔
148
        ];
1✔
149
        let spans: Vec<&Span> = spans.iter().collect();
2✔
150
        assert_eq!(wrap_spans(&spans, 32).len(), 2);
2✔
151
        // Let's make a sentence, which requires 3 lines, but with only one span
152
        let spans: Vec<Span> = vec![Span::from(
2✔
153
            "Hello everybody! My name is Uncle Camel. How's it going today?",
1✔
154
        )];
1✔
155
        let spans: Vec<&Span> = spans.iter().collect();
2✔
156
        // makes Hello everybody, my name is uncle, camel. how's it, goind today
1✔
157
        assert_eq!(wrap_spans(&spans, 16).len(), 4);
2✔
158
        // Combine
159
        let spans: Vec<Span> = vec![
2✔
160
            Span::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
2✔
161
            Span::from("Canem!"),
2✔
162
            Span::from("In posuere sollicitudin vulputate"),
2✔
163
            Span::from("Sed vitae rutrum quam."),
2✔
164
        ];
1✔
165
        let spans: Vec<&Span> = spans.iter().collect();
2✔
166
        // "Lorem ipsum dolor sit amet,", "consectetur adipiscing elit. Canem!", "In posuere sollicitudin vulputate", "Sed vitae rutrum quam."
1✔
167
        assert_eq!(wrap_spans(&spans, 36).len(), 4);
2✔
168
    }
2✔
169

170
    #[test]
171
    fn wrap_spans_should_preserve_style_if_wrapped() {
2✔
172
        let input = [
2✔
173
            Span::styled("hello there", Style::new().fg(Color::Black)),
2✔
174
            Span::raw("test"),
2✔
175
        ];
2✔
176
        let input = input.iter().collect::<Vec<_>>();
2✔
177
        let res = wrap_spans(&input, 5);
2✔
178
        assert_eq!(res.len(), 3);
2✔
179
        assert_eq!(
2✔
180
            res[0],
2✔
181
            Line::from(Span::styled("hello", Style::new().fg(Color::Black)))
2✔
182
        );
1✔
183
        assert_eq!(
2✔
184
            res[1],
2✔
185
            Line::from(Span::styled("there", Style::new().fg(Color::Black)))
2✔
186
        );
1✔
187
        assert_eq!(res[2], Line::from(Span::raw("test")));
2✔
188
    }
2✔
189

190
    #[test]
191
    fn test_components_utils_get_block() {
2✔
192
        let borders = Borders::default()
2✔
193
            .sides(BorderSides::ALL)
2✔
194
            .color(Color::Red)
2✔
195
            .modifiers(BorderType::Rounded);
2✔
196
        let _ = get_block(
2✔
197
            borders,
2✔
198
            Some(&Title::from("title").alignment(HorizontalAlignment::Center)),
2✔
199
            true,
2✔
200
            None,
2✔
201
        );
2✔
202
        let _ = get_block(borders, None, false, None);
2✔
203
    }
2✔
204

205
    #[test]
206
    fn test_components_utils_calc_utf8_cursor_position() {
2✔
207
        let chars: Vec<char> = vec!['v', 'e', 'e', 's', 'o'];
2✔
208
        // Entire
1✔
209
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 5);
2✔
210
        assert_eq!(calc_utf8_cursor_position(&chars[0..3]), 3);
2✔
211
        // With special characters
212
        let chars: Vec<char> = vec!['я', ' ', 'х', 'о', 'ч', 'у', ' ', 'с', 'п', 'а', 'т', 'ь'];
2✔
213
        assert_eq!(calc_utf8_cursor_position(&chars[0..6]), 6);
2✔
214
        let chars: Vec<char> = vec!['H', 'i', '😄'];
2✔
215
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 4);
2✔
216
        let chars: Vec<char> = vec!['我', '之', '😄'];
2✔
217
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 6);
2✔
218
    }
2✔
219
}
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