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