Skip to main content

mermaid_cli/render/widgets/
status_line.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::Style,
5    text::{Line, Span},
6    widgets::{Paragraph, Widget},
7};
8use std::collections::VecDeque;
9
10use super::GenerationStatus;
11use crate::render::theme::Theme;
12
13/// Props for StatusLineWidget (stateless widget showing generation progress)
14pub struct StatusLineWidget<'a> {
15    pub status: GenerationStatus,
16    pub elapsed_secs: u64,
17    pub tokens_received: usize,
18    /// Whether tokens_received is an estimate (show ~ prefix)
19    pub tokens_estimated: bool,
20    pub theme: &'a Theme,
21    /// Queued messages waiting to be processed
22    pub queued_messages: &'a VecDeque<String>,
23}
24
25impl<'a> Widget for StatusLineWidget<'a> {
26    fn render(self, area: Rect, buf: &mut Buffer) {
27        // Don't render if area is too small
28        if area.height == 0 || area.width < 10 {
29            return;
30        }
31
32        // Only render if status is not Idle
33        if self.status == GenerationStatus::Idle {
34            return;
35        }
36
37        let status_text = self.status.display_text();
38
39        let info_color = self.theme.colors.info.to_color();
40
41        // Determine arrow direction based on state
42        let (arrow, flow_direction) = match self.status {
43            GenerationStatus::Sending | GenerationStatus::Thinking => ("↑ ", "upstream"),
44            GenerationStatus::Streaming => ("↓ ", "downstream"),
45            GenerationStatus::Idle => ("", ""),
46        };
47
48        let spans = vec![
49            // Arrow indicator showing message direction (cyan)
50            Span::styled(arrow, Style::new().fg(info_color)),
51            // Status text with ellipsis (cyan)
52            Span::styled(format!("{}... ", status_text), Style::new().fg(info_color)),
53            // Metadata in parentheses (dimmed)
54            // Show ~ prefix when tokens are estimated (during streaming)
55            Span::styled(
56                format!(
57                    "(esc to interrupt • {}s • {} {}{} tokens)",
58                    self.elapsed_secs,
59                    if flow_direction == "downstream" {
60                        "↓"
61                    } else {
62                        "↑"
63                    },
64                    if self.tokens_estimated { "~" } else { "" },
65                    self.tokens_received
66                ),
67                Style::new()
68                    .fg(self.theme.colors.text_secondary.to_color())
69                    .dim(),
70            ),
71        ];
72
73        let mut lines = vec![Line::from(spans)];
74
75        // Show all queued messages below the status line with highlight
76        let max_len = area.width.saturating_sub(4) as usize;
77        for queued in self.queued_messages.iter() {
78            // Truncate long messages to fit in the area
79            let display_msg = if queued.len() > max_len {
80                let end = queued.floor_char_boundary(max_len.saturating_sub(3));
81                format!("> {}...", &queued[..end])
82            } else {
83                format!("> {}", queued)
84            };
85
86            lines.push(Line::from(vec![Span::styled(
87                display_msg,
88                Style::new()
89                    .fg(self.theme.colors.text_primary.to_color())
90                    .bg(ratatui::style::Color::Rgb(60, 60, 80)), // Subtle purple highlight
91            )]));
92        }
93
94        let paragraph = Paragraph::new(lines);
95
96        paragraph.render(area, buf);
97    }
98}