wisp/components/
thought_message.rs1use tui::{FitOptions, Frame, 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) -> Frame {
19 if self.text.is_empty() {
20 return Frame::empty();
21 }
22
23 let lines = Self::format_lines(self.text, &context.theme);
24 Frame::new(lines).fit(context.size.width, FitOptions::wrap())
25 }
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31
32 #[test]
33 fn renders_italic_muted_thought_line() {
34 let component = ThoughtMessage { text: "check plan" };
35 let context = ViewContext::new((80, 24));
36 let frame = component.render(&context);
37 let lines = frame.lines();
38 assert_eq!(lines.len(), 1);
39 assert_eq!(lines[0].plain_text(), "check plan");
40 let style = lines[0].spans()[0].style();
41 assert_eq!(style.fg, Some(context.theme.muted()));
42 assert!(style.italic);
43 }
44
45 #[test]
46 fn renders_all_lines_as_italic_muted() {
47 let component = ThoughtMessage { text: "line one\nline two" };
48 let context = ViewContext::new((80, 24));
49 let frame = component.render(&context);
50 let lines = frame.lines();
51 assert_eq!(lines.len(), 2);
52 assert_eq!(lines[0].plain_text(), "line one");
53 assert_eq!(lines[1].plain_text(), "line two");
54 for line in lines {
55 let style = line.spans()[0].style();
56 assert_eq!(style.fg, Some(context.theme.muted()));
57 assert!(style.italic);
58 }
59 }
60
61 #[test]
62 fn wrapped_continuation_rows_remain_italic_muted() {
63 let component = ThoughtMessage { text: "abcdefghijklmnopqrstuvwxyz" };
64 let context = ViewContext::new((80, 24));
65 let frame = component.render(&context);
66 let wrapped = frame.lines()[0].soft_wrap(12);
67 assert!(wrapped.len() > 1);
68
69 for row in wrapped.iter().skip(1) {
70 assert!(!row.spans().is_empty());
71 assert!(
72 row.spans()
73 .iter()
74 .all(|span| { span.style().fg == Some(context.theme.muted()) && span.style().italic })
75 );
76 }
77 }
78}