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::ui::theme;
13
14fn format_duration(d: Duration) -> String {
15 let micros = d.as_micros();
16 if micros < 1000 {
17 format!("{micros}\u{03bc}s")
18 } else {
19 format!("{:.1}ms", micros as f64 / 1000.0)
20 }
21}
22
23pub struct StatusBar {
24 pub engine: EngineKind,
25 pub match_count: usize,
26 pub flags: EngineFlags,
27 pub show_whitespace: bool,
28 pub compile_time: Option<Duration>,
29 pub match_time: Option<Duration>,
30}
31
32impl Widget for StatusBar {
33 fn render(self, area: Rect, buf: &mut Buffer) {
34 let mut spans = vec![
35 Span::styled(
36 format!(" {} ", self.engine),
37 Style::default()
38 .fg(theme::BASE)
39 .bg(theme::BLUE)
40 .add_modifier(Modifier::BOLD),
41 ),
42 Span::styled(" ", Style::default().bg(theme::SURFACE0)),
43 Span::styled(
44 format!(
45 " {} match{} ",
46 self.match_count,
47 if self.match_count == 1 { "" } else { "es" }
48 ),
49 Style::default().fg(theme::TEXT).bg(theme::SURFACE0),
50 ),
51 Span::styled(" ", Style::default().bg(theme::SURFACE0)),
52 ];
53
54 if self.compile_time.is_some() || self.match_time.is_some() {
56 let mut parts = Vec::new();
57 if let Some(ct) = self.compile_time {
58 parts.push(format!("compile: {}", format_duration(ct)));
59 }
60 if let Some(mt) = self.match_time {
61 parts.push(format!("match: {}", format_duration(mt)));
62 }
63 spans.push(Span::styled(
64 format!("{} ", parts.join(" | ")),
65 Style::default().fg(theme::SUBTEXT).bg(theme::SURFACE0),
66 ));
67 }
68
69 let flags = [
71 ("i", self.flags.case_insensitive),
72 ("m", self.flags.multi_line),
73 ("s", self.flags.dot_matches_newline),
74 ("u", self.flags.unicode),
75 ("x", self.flags.extended),
76 ];
77
78 for (name, active) in &flags {
79 let style = if *active {
80 Style::default()
81 .fg(theme::BASE)
82 .bg(theme::GREEN)
83 .add_modifier(Modifier::BOLD)
84 } else {
85 Style::default().fg(theme::OVERLAY).bg(theme::SURFACE0)
86 };
87 spans.push(Span::styled(format!(" {name} "), style));
88 }
89
90 if self.show_whitespace {
91 spans.push(Span::styled(
92 " \u{00b7} ",
93 Style::default()
94 .fg(theme::BASE)
95 .bg(theme::TEAL)
96 .add_modifier(Modifier::BOLD),
97 ));
98 }
99
100 spans.push(Span::styled(
101 " | Tab: switch | Ctrl+E: engine | Ctrl+W: ws | F1: help ",
102 Style::default().fg(theme::SUBTEXT).bg(theme::SURFACE0),
103 ));
104
105 let line = Line::from(spans);
106 let paragraph = Paragraph::new(line).style(Style::default().bg(theme::SURFACE0));
107 paragraph.render(area, buf);
108 }
109}