stynx_code_tui/widgets/
sidebar_info.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Modifier, Style},
5 text::{Line, Span},
6 widgets::{Paragraph, Widget},
7};
8
9use crate::theme;
10use crate::widgets::footer::{fmt_elapsed_short, pretty_model, shrink_path};
11use crate::widgets::spinner::FRAMES;
12
13pub struct SidebarInfo<'a> {
16 pub cwd: &'a str,
17 pub model: &'a str,
18 pub mode: &'a str,
19 pub cost: f64,
20 pub git_branch: Option<&'a str>,
21 pub is_streaming: bool,
22 pub is_pending: bool,
23 pub is_paused: bool,
24 pub spinner_frame: usize,
25 pub elapsed_secs: u64,
26}
27
28fn row(icon: &str, value: impl Into<String>, color: Color) -> Line<'static> {
29 Line::from(vec![
30 Span::styled(
31 format!(" {icon} "),
32 Style::default().fg(color).add_modifier(Modifier::BOLD),
33 ),
34 Span::styled(value.into(), Style::default().fg(theme::TEXT())),
35 ])
36}
37
38impl<'a> Widget for SidebarInfo<'a> {
39 fn render(self, area: Rect, buf: &mut Buffer) {
40 if area.height == 0 || area.width < 4 {
41 return;
42 }
43 let bg = theme::BACKGROUND();
44 for y in area.y..area.y + area.height {
45 for x in area.x..area.x + area.width {
46 buf[(x, y)].set_style(Style::default().bg(bg));
47 }
48 }
49
50 let w = area.width as usize;
51 let mut lines: Vec<Line<'static>> = Vec::new();
52
53 lines.push(Line::from(Span::styled(
55 "\u{2500}".repeat(w.saturating_sub(2)),
56 Style::default().fg(theme::OVERLAY()),
57 )));
58
59 if self.is_paused {
61 lines.push(row("\u{23F8}", "paused", theme::GOLD()));
62 } else if self.is_pending {
63 let ch = FRAMES[self.spinner_frame % FRAMES.len()];
64 lines.push(row(&ch.to_string(), "connecting…", theme::SUBTLE()));
65 } else if self.is_streaming {
66 let ch = FRAMES[self.spinner_frame % FRAMES.len()];
67 lines.push(row(
68 &ch.to_string(),
69 format!("generating {}", fmt_elapsed_short(self.elapsed_secs)),
70 theme::FOAM(),
71 ));
72 }
73
74 lines.push(row("\u{25C7}", pretty_model(self.model), theme::IRIS()));
76
77 if self.mode != "Normal" {
79 let (icon, color) = match self.mode {
80 "Auto-accept" => ("\u{26A1}", theme::GOLD()),
81 "Plan" => ("\u{25C6}", theme::IRIS()),
82 "Bypass" => ("\u{26A0}", theme::LOVE()),
83 _ => ("\u{25CF}", theme::FOAM()),
84 };
85 lines.push(row(icon, self.mode.to_string(), color));
86 }
87
88 if let Some(branch) = self.git_branch {
90 lines.push(row("\u{E0A0}", branch.to_string(), theme::FOAM()));
91 }
92
93 lines.push(row(
95 "\u{F07C}",
96 shrink_path(self.cwd, w.saturating_sub(4)),
97 theme::SUBTLE(),
98 ));
99
100 lines.push(row("\u{0024}", format!("{:.4}", self.cost), theme::IRIS()));
102
103 Paragraph::new(lines)
104 .style(Style::default().bg(bg))
105 .render(area, buf);
106 }
107}