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::RunningTools => ("• ", "tools"),
46            GenerationStatus::Compacting => ("• ", "compaction"),
47            GenerationStatus::Cancelling => ("• ", "cleanup"),
48            GenerationStatus::Idle => ("", ""),
49        };
50
51        let spans = vec![
52            // Arrow indicator showing message direction (cyan)
53            Span::styled(arrow, Style::new().fg(info_color)),
54            // Status text with ellipsis (cyan)
55            Span::styled(format!("{}... ", status_text), Style::new().fg(info_color)),
56            // Metadata in parentheses (dimmed)
57            // Show ~ prefix when tokens are estimated (during streaming)
58            Span::styled(
59                format!(
60                    "(esc to interrupt • {}s • {} {}{} tokens)",
61                    self.elapsed_secs,
62                    if flow_direction == "downstream" {
63                        "↓"
64                    } else if flow_direction == "tools" {
65                        "tools"
66                    } else if flow_direction == "compaction" {
67                        "compact"
68                    } else if flow_direction == "cleanup" {
69                        "cleanup"
70                    } else {
71                        "↑"
72                    },
73                    if self.tokens_estimated { "~" } else { "" },
74                    self.tokens_received
75                ),
76                Style::new()
77                    .fg(self.theme.colors.text_secondary.to_color())
78                    .dim(),
79            ),
80        ];
81
82        let mut lines = vec![Line::from(spans)];
83
84        // Show all queued messages below the status line with highlight
85        let max_len = area.width.saturating_sub(4) as usize;
86        for queued in self.queued_messages.iter() {
87            // Truncate long messages to fit in the area
88            let display_msg = if queued.len() > max_len {
89                let end = queued.floor_char_boundary(max_len.saturating_sub(3));
90                format!("> {}...", &queued[..end])
91            } else {
92                format!("> {}", queued)
93            };
94
95            lines.push(Line::from(vec![Span::styled(
96                display_msg,
97                Style::new()
98                    .fg(self.theme.colors.text_primary.to_color())
99                    .bg(ratatui::style::Color::Rgb(60, 60, 80)), // Subtle purple highlight
100            )]));
101        }
102
103        let paragraph = Paragraph::new(lines);
104
105        paragraph.render(area, buf);
106    }
107}