mermaid_cli/render/widgets/
status_line.rs1use 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
13pub struct StatusLineWidget<'a> {
15 pub status: GenerationStatus,
16 pub elapsed_secs: u64,
17 pub tokens_received: usize,
18 pub tokens_estimated: bool,
20 pub theme: &'a Theme,
21 pub queued_messages: &'a VecDeque<String>,
23 pub active_tool: Option<String>,
26}
27
28impl<'a> Widget for StatusLineWidget<'a> {
29 fn render(self, area: Rect, buf: &mut Buffer) {
30 if area.height == 0 || area.width < 10 {
32 return;
33 }
34
35 if self.status == GenerationStatus::Idle {
37 return;
38 }
39
40 let status_text = match (&self.status, &self.active_tool) {
43 (GenerationStatus::RunningTools, Some(tool)) => {
44 format!("{}: {}", self.status.display_text(), tool)
45 },
46 _ => self.status.display_text().to_string(),
47 };
48
49 let info_color = self.theme.colors.info.to_color();
50
51 let (arrow, flow_direction) = match self.status {
53 GenerationStatus::Sending | GenerationStatus::Thinking => ("↑ ", "upstream"),
54 GenerationStatus::Streaming => ("↓ ", "downstream"),
55 GenerationStatus::RunningTools => ("• ", "tools"),
56 GenerationStatus::Compacting => ("• ", "compaction"),
57 GenerationStatus::Cancelling => ("• ", "cleanup"),
58 GenerationStatus::Idle => ("", ""),
59 };
60
61 let bg_hint = if self.status == GenerationStatus::RunningTools {
64 " • ctrl+b to background"
65 } else {
66 ""
67 };
68
69 let spans = vec![
70 Span::styled(arrow, Style::new().fg(info_color)),
72 Span::styled(format!("{}... ", status_text), Style::new().fg(info_color)),
74 Span::styled(
77 format!(
78 "(esc to interrupt{bg_hint} • {}s • {} {}{} tokens)",
79 self.elapsed_secs,
80 if flow_direction == "downstream" {
81 "↓"
82 } else if flow_direction == "tools" {
83 "tools"
84 } else if flow_direction == "compaction" {
85 "compact"
86 } else if flow_direction == "cleanup" {
87 "cleanup"
88 } else {
89 "↑"
90 },
91 if self.tokens_estimated { "~" } else { "" },
92 self.tokens_received
93 ),
94 Style::new()
95 .fg(self.theme.colors.text_secondary.to_color())
96 .dim(),
97 ),
98 ];
99
100 let mut lines = vec![Line::from(spans)];
101
102 let max_len = area.width.saturating_sub(4) as usize;
104 for queued in self.queued_messages.iter() {
105 let display_msg = if queued.len() > max_len {
107 let end = queued.floor_char_boundary(max_len.saturating_sub(3));
108 format!("> {}...", &queued[..end])
109 } else {
110 format!("> {}", queued)
111 };
112
113 lines.push(Line::from(vec![Span::styled(
114 display_msg,
115 Style::new()
116 .fg(self.theme.colors.text_primary.to_color())
117 .bg(ratatui::style::Color::Rgb(60, 60, 80)), )]));
119 }
120
121 let paragraph = Paragraph::new(lines);
122
123 paragraph.render(area, buf);
124 }
125}