Skip to main content

wisp/components/
thought_message.rs

1use tui::{Line, Style, Theme, ViewContext};
2
3pub struct ThoughtMessage<'a> {
4    pub text: &'a str,
5}
6
7impl ThoughtMessage<'_> {
8    fn format_line(text: &str, theme: &Theme) -> Line {
9        Line::with_style(text, Style::fg(theme.muted()).italic())
10    }
11
12    fn format_lines(text: &str, theme: &Theme) -> Vec<Line> {
13        text.lines().map(|line| Self::format_line(line, theme)).collect()
14    }
15}
16
17impl ThoughtMessage<'_> {
18    pub fn render(&self, context: &ViewContext) -> Vec<Line> {
19        if self.text.is_empty() {
20            return vec![];
21        }
22
23        Self::format_lines(self.text, &context.theme)
24    }
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn renders_italic_muted_thought_line() {
33        let component = ThoughtMessage { text: "check plan" };
34        let context = ViewContext::new((80, 24));
35        let lines = component.render(&context);
36        assert_eq!(lines.len(), 1);
37        assert_eq!(lines[0].plain_text(), "check plan");
38        let style = lines[0].spans()[0].style();
39        assert_eq!(style.fg, Some(context.theme.muted()));
40        assert!(style.italic);
41    }
42
43    #[test]
44    fn renders_all_lines_as_italic_muted() {
45        let component = ThoughtMessage { text: "line one\nline two" };
46        let context = ViewContext::new((80, 24));
47        let lines = component.render(&context);
48        assert_eq!(lines.len(), 2);
49        assert_eq!(lines[0].plain_text(), "line one");
50        assert_eq!(lines[1].plain_text(), "line two");
51        for line in &lines {
52            let style = line.spans()[0].style();
53            assert_eq!(style.fg, Some(context.theme.muted()));
54            assert!(style.italic);
55        }
56    }
57
58    #[test]
59    fn wrapped_continuation_rows_remain_italic_muted() {
60        let component = ThoughtMessage { text: "abcdefghijklmnopqrstuvwxyz" };
61        let context = ViewContext::new((80, 24));
62        let lines = component.render(&context);
63        let wrapped = lines[0].soft_wrap(12);
64        assert!(wrapped.len() > 1);
65
66        for row in wrapped.iter().skip(1) {
67            assert!(!row.spans().is_empty());
68            assert!(
69                row.spans()
70                    .iter()
71                    .all(|span| { span.style().fg == Some(context.theme.muted()) && span.style().italic })
72            );
73        }
74    }
75}