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

jzombie / term-wm / 20796173160

07 Jan 2026 08:53PM UTC coverage: 38.847% (+2.9%) from 35.923%
20796173160

Pull #5

github

web-flow
Merge 6220a88ab into bbd85351a
Pull Request #5: Add splash screen, debug logging, and unified scroll support

603 of 1185 new or added lines in 16 files covered. (50.89%)

8 existing lines in 6 files now uncovered.

3018 of 7769 relevant lines covered (38.85%)

2.96 hits per line

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

84.3
/src/components/text_renderer.rs
1
use ratatui::layout::Rect;
2
use ratatui::text::{Line, Text};
3
use ratatui::widgets::{Paragraph, Wrap};
4

5
use crate::components::{Component, scroll_view::ScrollViewComponent};
6
use crate::ui::UiFrame;
7

8
#[derive(Debug)]
9
pub struct TextRendererComponent {
10
    text: Text<'static>,
11
    scroll: ScrollViewComponent,
12
    wrap: bool,
13
}
14

15
impl Component for TextRendererComponent {
NEW
16
    fn resize(&mut self, area: Rect) {
×
NEW
17
        self.scroll.set_fixed_height(Some(area.height));
×
NEW
18
    }
×
19

20
    fn render(&mut self, frame: &mut UiFrame<'_>, area: Rect, _focused: bool) {
3✔
21
        if area.width == 0 || area.height == 0 {
3✔
NEW
22
            return;
×
23
        }
3✔
24

25
        let view = area.height as usize;
3✔
26
        // Determine content width and whether vertical scrollbar is needed.
27
        let mut content_width = area.width;
3✔
28

29
        // Compute totals depending on wrap mode.
30
        let (mut v_total, mut h_total) = if self.wrap {
3✔
31
            // when wrapping, compute display lines after wrapping and set h_total to content width
32
            let total = compute_display_lines(&self.text, content_width);
3✔
33
            (total, content_width as usize)
3✔
34
        } else {
35
            // no wrapping: each Text line maps to one visual line; compute longest width for h_total
NEW
36
            let total = self.text.lines.len().max(1);
×
NEW
37
            let longest = self.text.lines.iter().map(|l| l.width()).max().unwrap_or(0);
×
NEW
38
            (total, longest)
×
39
        };
40

41
        let v_scroll_needed = v_total > view && content_width > 0;
3✔
42
        if v_scroll_needed {
3✔
43
            content_width = content_width.saturating_sub(1);
3✔
44
            if self.wrap {
3✔
45
                v_total = compute_display_lines(&self.text, content_width);
3✔
46
            }
3✔
NEW
47
        }
×
48

49
        // If wrapping is enabled, horizontal total should reflect the final content width
50
        if self.wrap {
3✔
51
            h_total = content_width as usize;
3✔
52
        }
3✔
53

54
        self.scroll.update(area, v_total, view);
3✔
55
        self.scroll
3✔
56
            .set_horizontal_total_view(h_total, content_width as usize);
3✔
57

58
        let v_off = self.scroll.offset() as u16;
3✔
59
        let h_off = self.scroll.h_offset() as u16;
3✔
60

61
        let mut paragraph = Paragraph::new(self.text.clone());
3✔
62
        if self.wrap {
3✔
63
            paragraph = paragraph.wrap(Wrap { trim: false });
3✔
64
        }
3✔
65
        paragraph = paragraph.scroll((v_off, h_off));
3✔
66
        frame.render_widget(
3✔
67
            paragraph,
3✔
68
            Rect {
3✔
69
                x: area.x,
3✔
70
                y: area.y,
3✔
71
                width: content_width,
3✔
72
                height: area.height,
3✔
73
            },
3✔
74
        );
75
        self.scroll.render(frame);
3✔
76
    }
3✔
77

78
    fn handle_event(&mut self, event: &crossterm::event::Event) -> bool {
2✔
79
        match event {
2✔
80
            crossterm::event::Event::Mouse(_) => {
81
                let resp = self.scroll.handle_event(event);
1✔
82
                if let Some(off) = resp.v_offset {
1✔
83
                    self.scroll.set_offset(off);
1✔
84
                }
1✔
85
                if let Some(off) = resp.h_offset {
1✔
NEW
86
                    self.scroll.set_h_offset(off);
×
87
                }
1✔
88
                resp.handled
1✔
89
            }
90
            crossterm::event::Event::Key(key) => self.scroll.handle_key_event(key),
1✔
NEW
91
            _ => false,
×
92
        }
93
    }
2✔
94
}
95

96
impl TextRendererComponent {
97
    pub fn new() -> Self {
7✔
98
        Self {
7✔
99
            text: Text::from(vec![Line::from(String::new())]),
7✔
100
            scroll: ScrollViewComponent::new(),
7✔
101
            wrap: true,
7✔
102
        }
7✔
103
    }
7✔
104

105
    pub fn set_text(&mut self, text: Text<'static>) {
4✔
106
        self.text = text;
4✔
107
    }
4✔
108

109
    pub fn set_wrap(&mut self, wrap: bool) {
7✔
110
        self.wrap = wrap;
7✔
111
    }
7✔
112

113
    pub fn set_keyboard_enabled(&mut self, enabled: bool) {
3✔
114
        self.scroll.set_keyboard_enabled(enabled);
3✔
115
    }
3✔
116

117
    pub fn offset(&self) -> usize {
6✔
118
        self.scroll.offset()
6✔
119
    }
6✔
120

121
    pub fn set_offset(&mut self, offset: usize) {
2✔
122
        self.scroll.set_offset(offset);
2✔
123
    }
2✔
124

NEW
125
    pub fn view(&self) -> usize {
×
NEW
126
        self.scroll.view()
×
NEW
127
    }
×
128

129
    pub fn update(&mut self, area: Rect, total: usize, view: usize) {
1✔
130
        self.scroll.set_fixed_height(Some(area.height));
1✔
131
        self.scroll.update(area, total, view);
1✔
132
    }
1✔
133

NEW
134
    pub fn set_horizontal_total_view(&mut self, total: usize, view: usize) {
×
NEW
135
        self.scroll.set_horizontal_total_view(total, view);
×
NEW
136
    }
×
137

138
    pub fn text_ref(&self) -> &Text<'static> {
1✔
139
        &self.text
1✔
140
    }
1✔
141

142
    pub fn rendered_lines(&self) -> Vec<String> {
1✔
143
        self.text
1✔
144
            .lines
1✔
145
            .iter()
1✔
146
            .map(|line| {
11✔
147
                line.spans
11✔
148
                    .iter()
11✔
149
                    .map(|span| span.content.to_string())
22✔
150
                    .collect::<String>()
11✔
151
            })
11✔
152
            .collect()
1✔
153
    }
1✔
154
}
155

156
fn compute_display_lines(text: &Text<'_>, width: u16) -> usize {
6✔
157
    let usable = width.max(1) as usize;
6✔
158
    text.lines
6✔
159
        .iter()
6✔
160
        .map(|line| {
66✔
161
            let w = line.width();
66✔
162
            if w == 0 {
66✔
163
                1
18✔
164
            } else {
165
                (w + usable - 1).div_euclid(usable)
48✔
166
            }
167
        })
66✔
168
        .sum::<usize>()
6✔
169
        .max(1)
6✔
170
}
6✔
171

172
impl Default for TextRendererComponent {
NEW
173
    fn default() -> Self {
×
NEW
174
        Self::new()
×
NEW
175
    }
×
176
}
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