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

veeso / tui-realm-stdlib / 15137208932

20 May 2025 12:13PM UTC coverage: 67.595% (-0.7%) from 68.289%
15137208932

push

github

web-flow
Fix clippy lints, apply some pedantic fixes and general small improvements (#32)

* style(examples): directly have values as a type instead of casting

* style(examples/utils): ignore unused warnings

* style: run clippy auto fix

and remove redundant tests from "label"

* style: apply some clippy pedantic auto fixes

* test: set specific strings for "should_panic"

So that other panics are catched as failed tests.

* style(bar_chart): remove casting to "u64" when "usize" is directly provided and needed

* refactor(table): move making rows to own function

To appease "clippy::too_many_lines" and slightly reduce nesting.

* style: add "#[must_use]" where applicable

To hint not to forget a value.

104 of 192 new or added lines in 19 files covered. (54.17%)

12 existing lines in 2 files now uncovered.

2985 of 4416 relevant lines covered (67.6%)

1.66 hits per line

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

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

5
// deps
6
extern crate textwrap;
7
extern crate unicode_width;
8
// local
9
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, TextModifiers, TextSpan};
10
use tuirealm::Props;
11
// ext
12
use tuirealm::ratatui::style::{Color, Modifier, Style};
13
use tuirealm::ratatui::text::Line as Spans;
14
use tuirealm::ratatui::text::Span;
15
use tuirealm::ratatui::widgets::Block;
16
use unicode_width::UnicodeWidthStr;
17

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

78
/// ### use_or_default_styles
79
///
80
/// Returns the styles to be used; in case in span are default, use props'.
81
/// The values returned are `(foreground, background, modifiers)`
82
#[must_use]
83
pub fn use_or_default_styles(props: &Props, span: &TextSpan) -> (Color, Color, Modifier) {
11✔
84
    (
85
        match span.fg {
11✔
86
            Color::Reset => props
10✔
87
                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
10✔
88
                .unwrap_color(),
10✔
89
            _ => span.fg,
1✔
90
        },
91
        match span.bg {
11✔
92
            Color::Reset => props
10✔
93
                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
10✔
94
                .unwrap_color(),
10✔
95
            _ => span.bg,
1✔
96
        },
97
        if span.modifiers.is_empty() {
11✔
98
            props
10✔
99
                .get_or(
10✔
100
                    Attribute::TextProps,
10✔
101
                    AttrValue::TextModifiers(TextModifiers::empty()),
10✔
102
                )
103
                .unwrap_text_modifiers()
10✔
104
        } else {
105
            span.modifiers
1✔
106
        },
107
    )
108
}
11✔
109

110
/// ### get_block
111
///
112
/// Construct a block for widget using block properties.
113
/// If focus is true the border color is applied, otherwise inactive_style
114
#[must_use]
115
pub fn get_block<T: AsRef<str>>(
2✔
116
    props: Borders,
2✔
117
    title: Option<&(T, Alignment)>,
2✔
118
    focus: bool,
2✔
119
    inactive_style: Option<Style>,
2✔
120
) -> Block {
2✔
121
    let title = title.map_or(("", Alignment::Left), |v| (v.0.as_ref(), v.1));
2✔
122
    Block::default()
2✔
123
        .borders(props.sides)
2✔
124
        .border_style(if focus {
2✔
125
            props.style()
1✔
126
        } else {
127
            inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
1✔
128
        })
129
        .border_type(props.modifiers)
2✔
130
        .title(title.0)
2✔
131
        .title_alignment(title.1)
2✔
132
}
2✔
133

134
/// Get the [`Attribute::Title`] or a Centered default
135
#[must_use]
136
pub fn get_title_or_center(props: &Props) -> (&str, Alignment) {
×
137
    props
×
138
        .get_ref(Attribute::Title)
×
139
        .and_then(|v| v.as_title())
×
NEW
140
        .map_or(("", Alignment::Center), |v| (v.0.as_str(), v.1))
×
141
}
×
142

143
/// ### calc_utf8_cursor_position
144
///
145
/// Calculate the UTF8 compliant position for the cursor given the characters preceeding the cursor position.
146
/// Use this function to calculate cursor position whenever you want to handle UTF8 texts with cursors
147
#[must_use]
148
pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
5✔
149
    chars.iter().collect::<String>().width() as u16
5✔
150
}
5✔
151

152
#[cfg(test)]
153
mod test {
154

155
    use super::*;
156
    use tuirealm::props::{Alignment, BorderSides, BorderType, Props};
157

158
    use pretty_assertions::assert_eq;
159

160
    #[test]
161
    fn test_components_utils_wrap_spans() {
1✔
162
        let mut props: Props = Props::default();
1✔
163
        props.set(
1✔
164
            Attribute::TextProps,
1✔
165
            AttrValue::TextModifiers(TextModifiers::BOLD),
1✔
166
        );
167
        props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
1✔
168
        props.set(Attribute::Background, AttrValue::Color(Color::White));
1✔
169
        // Prepare spans; let's start with two simple spans, which fits the line
170
        let spans: Vec<TextSpan> = vec![TextSpan::from("hello, "), TextSpan::from("world!")];
1✔
171
        let spans: Vec<&TextSpan> = spans.iter().collect();
1✔
172
        assert_eq!(wrap_spans(&spans, 64, &props).len(), 1);
1✔
173
        // Let's make a sentence, which would require two lines
174
        let spans: Vec<TextSpan> = vec![
1✔
175
            TextSpan::from("Hello, everybody, I'm Uncle Camel!"),
1✔
176
            TextSpan::from("How's it going today?"),
1✔
177
        ];
178
        let spans: Vec<&TextSpan> = spans.iter().collect();
1✔
179
        assert_eq!(wrap_spans(&spans, 32, &props).len(), 2);
1✔
180
        // Let's make a sentence, which requires 3 lines, but with only one span
181
        let spans: Vec<TextSpan> = vec![TextSpan::from(
1✔
182
            "Hello everybody! My name is Uncle Camel. How's it going today?",
183
        )];
184
        let spans: Vec<&TextSpan> = spans.iter().collect();
1✔
185
        // makes Hello everybody, my name is uncle, camel. how's it, goind today
186
        assert_eq!(wrap_spans(&spans, 16, &props).len(), 4);
1✔
187
        // Combine
188
        let spans: Vec<TextSpan> = vec![
1✔
189
            TextSpan::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
1✔
190
            TextSpan::from("Canem!"),
1✔
191
            TextSpan::from("In posuere sollicitudin vulputate"),
1✔
192
            TextSpan::from("Sed vitae rutrum quam."),
1✔
193
        ];
194
        let spans: Vec<&TextSpan> = spans.iter().collect();
1✔
195
        // "Lorem ipsum dolor sit amet,", "consectetur adipiscing elit. Canem!", "In posuere sollicitudin vulputate", "Sed vitae rutrum quam."
196
        assert_eq!(wrap_spans(&spans, 36, &props).len(), 4);
1✔
197
    }
1✔
198

199
    #[test]
200
    fn test_components_utils_use_or_default_styles() {
1✔
201
        let mut props: Props = Props::default();
1✔
202
        props.set(
1✔
203
            Attribute::TextProps,
1✔
204
            AttrValue::TextModifiers(TextModifiers::BOLD),
1✔
205
        );
206
        props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
1✔
207
        props.set(Attribute::Background, AttrValue::Color(Color::White));
1✔
208
        let span: TextSpan = TextSpan::from("test")
1✔
209
            .underlined()
1✔
210
            .fg(Color::Yellow)
1✔
211
            .bg(Color::Cyan);
1✔
212
        // Not-default
213
        let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
1✔
214
        assert_eq!(fg, Color::Yellow);
1✔
215
        assert_eq!(bg, Color::Cyan);
1✔
216
        assert!(modifiers.intersects(Modifier::UNDERLINED));
1✔
217
        // Default
218
        let span: TextSpan = TextSpan::from("test");
1✔
219
        let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
1✔
220
        assert_eq!(fg, Color::Red);
1✔
221
        assert_eq!(bg, Color::White);
1✔
222
        assert!(modifiers.intersects(Modifier::BOLD));
1✔
223
    }
1✔
224

225
    #[test]
226
    fn test_components_utils_get_block() {
1✔
227
        let props = Borders::default()
1✔
228
            .sides(BorderSides::ALL)
1✔
229
            .color(Color::Red)
1✔
230
            .modifiers(BorderType::Rounded);
1✔
231
        let _ = get_block(
1✔
232
            props.clone(),
1✔
233
            Some(&("title", Alignment::Center)),
1✔
234
            true,
1✔
235
            None,
1✔
236
        );
1✔
237
        let _ = get_block::<&str>(props, None, false, None);
1✔
238
    }
1✔
239

240
    #[test]
241
    fn test_components_utils_calc_utf8_cursor_position() {
1✔
242
        let chars: Vec<char> = vec!['v', 'e', 'e', 's', 'o'];
1✔
243
        // Entire
244
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 5);
1✔
245
        assert_eq!(calc_utf8_cursor_position(&chars[0..3]), 3);
1✔
246
        // With special characters
247
        let chars: Vec<char> = vec!['я', ' ', 'х', 'о', 'ч', 'у', ' ', 'с', 'п', 'а', 'т', 'ь'];
1✔
248
        assert_eq!(calc_utf8_cursor_position(&chars[0..6]), 6);
1✔
249
        let chars: Vec<char> = vec!['H', 'i', '😄'];
1✔
250
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 4);
1✔
251
        let chars: Vec<char> = vec!['我', '之', '😄'];
1✔
252
        assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 6);
1✔
253
    }
1✔
254
}
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