Skip to main content

rgx/ui/
status_bar.rs

1use std::time::Duration;
2
3use ratatui::{
4    buffer::Buffer,
5    layout::Rect,
6    style::{Modifier, Style},
7    text::{Line, Span},
8    widgets::{Paragraph, Widget},
9};
10
11use crate::engine::{EngineFlags, EngineKind};
12use crate::input::vim::VimMode;
13use crate::ui::theme;
14
15pub fn format_duration(d: Duration) -> String {
16    let micros = d.as_micros();
17    if micros < 1000 {
18        format!("{micros}\u{03bc}s")
19    } else {
20        format!("{:.1}ms", micros as f64 / 1000.0)
21    }
22}
23
24pub struct StatusBar {
25    pub engine: EngineKind,
26    pub match_count: usize,
27    pub flags: EngineFlags,
28    pub show_whitespace: bool,
29    pub compile_time: Option<Duration>,
30    pub match_time: Option<Duration>,
31    pub vim_mode: Option<VimMode>,
32}
33
34impl Widget for StatusBar {
35    fn render(self, area: Rect, buf: &mut Buffer) {
36        let mut spans = Vec::new();
37
38        if let Some(mode) = self.vim_mode {
39            let (mode_text, mode_bg) = match mode {
40                VimMode::Insert => (" INSERT ", theme::GREEN),
41                VimMode::Normal => (" NORMAL ", theme::BLUE),
42            };
43            spans.push(Span::styled(
44                mode_text,
45                Style::default()
46                    .fg(theme::BASE)
47                    .bg(mode_bg)
48                    .add_modifier(Modifier::BOLD),
49            ));
50            spans.push(Span::styled(" ", Style::default().bg(theme::SURFACE0)));
51        }
52
53        spans.push(Span::styled(
54            format!(" {} ", self.engine),
55            Style::default()
56                .fg(theme::BASE)
57                .bg(theme::BLUE)
58                .add_modifier(Modifier::BOLD),
59        ));
60        spans.push(Span::styled(" ", Style::default().bg(theme::SURFACE0)));
61        spans.push(Span::styled(
62            format!(
63                " {} match{} ",
64                self.match_count,
65                if self.match_count == 1 { "" } else { "es" }
66            ),
67            Style::default().fg(theme::TEXT).bg(theme::SURFACE0),
68        ));
69        spans.push(Span::styled(" ", Style::default().bg(theme::SURFACE0)));
70
71        // Timing info
72        if self.compile_time.is_some() || self.match_time.is_some() {
73            let mut parts = Vec::new();
74            if let Some(ct) = self.compile_time {
75                parts.push(format!("compile: {}", format_duration(ct)));
76            }
77            if let Some(mt) = self.match_time {
78                parts.push(format!("match: {}", format_duration(mt)));
79            }
80            spans.push(Span::styled(
81                format!("{} ", parts.join(" | ")),
82                Style::default().fg(theme::SUBTEXT).bg(theme::SURFACE0),
83            ));
84        }
85
86        // Flag indicators
87        let flags = [
88            ("i", self.flags.case_insensitive),
89            ("m", self.flags.multi_line),
90            ("s", self.flags.dot_matches_newline),
91            ("u", self.flags.unicode),
92            ("x", self.flags.extended),
93        ];
94
95        for (name, active) in &flags {
96            let style = if *active {
97                Style::default()
98                    .fg(theme::BASE)
99                    .bg(theme::GREEN)
100                    .add_modifier(Modifier::BOLD)
101            } else {
102                Style::default().fg(theme::OVERLAY).bg(theme::SURFACE0)
103            };
104            spans.push(Span::styled(format!(" {name} "), style));
105        }
106
107        if self.show_whitespace {
108            spans.push(Span::styled(
109                " \u{00b7} ",
110                Style::default()
111                    .fg(theme::BASE)
112                    .bg(theme::TEAL)
113                    .add_modifier(Modifier::BOLD),
114            ));
115        }
116
117        spans.push(Span::styled(
118            " | Tab: switch | Ctrl+E: engine | Ctrl+W: ws | F1: help ",
119            Style::default().fg(theme::SUBTEXT).bg(theme::SURFACE0),
120        ));
121
122        let line = Line::from(spans);
123        let paragraph = Paragraph::new(line).style(Style::default().bg(theme::SURFACE0));
124        paragraph.render(area, buf);
125    }
126}