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

veeso / tui-realm-stdlib / 22147846375

18 Feb 2026 01:42PM UTC coverage: 76.863% (-0.4%) from 77.303%
22147846375

push

github

hasezoey
style(prop_ext): add module documentation

3940 of 5126 relevant lines covered (76.86%)

7.45 hits per line

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

95.83
/src/utils.rs
1
//! Utilities functions to work with components
2

3
use std::borrow::Cow;
4

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

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

61
/// Construct a [`Block`] widget from the given properties.
62
///
63
/// If `focus` is `true`, [`Borders::style`] is applied as the Border style, if `false` `inactive_style` is applied, if `Some`.
64
#[must_use]
65
pub fn get_block(
9✔
66
    props: Borders,
9✔
67
    title: Option<&Title>,
9✔
68
    focus: bool,
9✔
69
    inactive_style: Option<Style>,
9✔
70
) -> Block<'_> {
9✔
71
    let mut block = Block::default()
9✔
72
        .borders(props.sides)
9✔
73
        .border_style(if focus {
9✔
74
            props.style()
3✔
75
        } else {
76
            // TODO: remove the default "Reset"
77
            inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
6✔
78
        })
79
        .border_type(props.modifiers);
9✔
80

81
    if let Some(title) = title {
9✔
82
        block = match title.position {
6✔
83
            TitlePosition::Top => block.title_top(borrow_clone_line(&title.content)),
3✔
84
            TitlePosition::Bottom => block.title_bottom(borrow_clone_line(&title.content)),
3✔
85
        };
86
    }
3✔
87

88
    block
9✔
89
}
9✔
90

91
/// Calculate the actual amount of terminal space taken up, taking into account UTF things like multi-width, undrawn and combinatory characters.
92
///
93
/// Use this function to calculate cursor position whenever you want to handle UTF8 texts with cursors
94
#[must_use]
95
pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
15✔
96
    chars.iter().collect::<String>().width() as u16
15✔
97
}
15✔
98

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

109
/// Convert a `&Line` to a `Line` by using [`Cow::Borrowed`].
110
pub fn borrow_clone_line<'a, 'b: 'a>(line: &'b Line<'a>) -> Line<'a> {
6✔
111
    Line {
6✔
112
        spans: line.spans.iter().map(borrow_clone_span).collect(),
6✔
113
        ..*line
6✔
114
    }
6✔
115
}
6✔
116

117
/// Convert a `&Text` to a `Text` by using [`Cow::Borrowed`].
118
pub fn borrow_clone_text<'a, 'b: 'a>(text: &'b Text<'a>) -> Text<'a> {
×
119
    Text {
×
120
        lines: text.lines.iter().map(borrow_clone_line).collect(),
×
121
        ..*text
×
122
    }
×
123
}
×
124

125
#[cfg(test)]
126
mod test {
127

128
    use super::*;
129
    use tuirealm::props::{BorderSides, BorderType, HorizontalAlignment};
130

131
    use pretty_assertions::assert_eq;
132

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

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

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

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