1pub mod explanation;
2pub mod match_display;
3pub mod regex_input;
4pub mod status_bar;
5pub mod test_input;
6pub mod theme;
7
8use ratatui::{
9 layout::{Constraint, Direction, Layout, Rect},
10 Frame,
11};
12
13use crate::app::App;
14use explanation::ExplanationPanel;
15use match_display::MatchDisplay;
16use regex_input::RegexInput;
17use status_bar::StatusBar;
18use test_input::TestInput;
19
20pub fn render(frame: &mut Frame, app: &App) {
21 let size = frame.area();
22
23 let main_chunks = Layout::default()
25 .direction(Direction::Vertical)
26 .constraints([
27 Constraint::Length(3), Constraint::Length(3), Constraint::Min(5), Constraint::Length(1), ])
32 .split(size);
33
34 let results_chunks = if main_chunks[2].width > 80 {
36 Layout::default()
37 .direction(Direction::Horizontal)
38 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
39 .split(main_chunks[2])
40 } else {
41 Layout::default()
43 .direction(Direction::Vertical)
44 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
45 .split(main_chunks[2])
46 };
47
48 if app.show_help {
50 render_help_overlay(frame, size);
51 return;
52 }
53
54 let error_str = app.error.as_deref();
55
56 frame.render_widget(
58 RegexInput {
59 editor: &app.regex_editor,
60 focused: app.focused_panel == 0,
61 error: error_str,
62 },
63 main_chunks[0],
64 );
65
66 frame.render_widget(
68 TestInput {
69 editor: &app.test_editor,
70 focused: app.focused_panel == 1,
71 matches: &app.matches,
72 },
73 main_chunks[1],
74 );
75
76 frame.render_widget(
78 MatchDisplay {
79 matches: &app.matches,
80 },
81 results_chunks[0],
82 );
83
84 frame.render_widget(
86 ExplanationPanel {
87 nodes: &app.explanation,
88 error: error_str,
89 },
90 results_chunks[1],
91 );
92
93 frame.render_widget(
95 StatusBar {
96 engine: app.engine_kind,
97 match_count: app.matches.len(),
98 flags: app.flags.clone(),
99 },
100 main_chunks[3],
101 );
102}
103
104fn render_help_overlay(frame: &mut Frame, area: Rect) {
105 use ratatui::{
106 style::Style,
107 text::{Line, Span},
108 widgets::{Block, Borders, Clear, Paragraph, Wrap},
109 };
110
111 let help_width = 60.min(area.width.saturating_sub(4));
112 let help_height = 18.min(area.height.saturating_sub(4));
113 let x = (area.width.saturating_sub(help_width)) / 2;
114 let y = (area.height.saturating_sub(help_height)) / 2;
115 let help_area = Rect::new(x, y, help_width, help_height);
116
117 frame.render_widget(Clear, help_area);
118
119 let lines = vec![
120 Line::from(Span::styled(
121 "rgx - Keyboard Shortcuts",
122 Style::default()
123 .fg(theme::BLUE)
124 .add_modifier(ratatui::style::Modifier::BOLD),
125 )),
126 Line::from(""),
127 Line::from(vec![
128 Span::styled("Tab ", Style::default().fg(theme::GREEN)),
129 Span::styled(
130 "Switch between pattern/test string",
131 Style::default().fg(theme::TEXT),
132 ),
133 ]),
134 Line::from(vec![
135 Span::styled("Ctrl+E ", Style::default().fg(theme::GREEN)),
136 Span::styled("Cycle regex engine", Style::default().fg(theme::TEXT)),
137 ]),
138 Line::from(vec![
139 Span::styled("Alt+i ", Style::default().fg(theme::GREEN)),
140 Span::styled("Toggle case-insensitive", Style::default().fg(theme::TEXT)),
141 ]),
142 Line::from(vec![
143 Span::styled("Alt+m ", Style::default().fg(theme::GREEN)),
144 Span::styled("Toggle multi-line", Style::default().fg(theme::TEXT)),
145 ]),
146 Line::from(vec![
147 Span::styled("Alt+s ", Style::default().fg(theme::GREEN)),
148 Span::styled(
149 "Toggle dot-matches-newline",
150 Style::default().fg(theme::TEXT),
151 ),
152 ]),
153 Line::from(vec![
154 Span::styled("Alt+u ", Style::default().fg(theme::GREEN)),
155 Span::styled("Toggle unicode mode", Style::default().fg(theme::TEXT)),
156 ]),
157 Line::from(vec![
158 Span::styled("Alt+x ", Style::default().fg(theme::GREEN)),
159 Span::styled("Toggle extended mode", Style::default().fg(theme::TEXT)),
160 ]),
161 Line::from(vec![
162 Span::styled("? ", Style::default().fg(theme::GREEN)),
163 Span::styled("Show/hide this help", Style::default().fg(theme::TEXT)),
164 ]),
165 Line::from(vec![
166 Span::styled("Esc ", Style::default().fg(theme::GREEN)),
167 Span::styled("Quit", Style::default().fg(theme::TEXT)),
168 ]),
169 Line::from(""),
170 Line::from(Span::styled(
171 "Press any key to close",
172 Style::default().fg(theme::SUBTEXT),
173 )),
174 ];
175
176 let block = Block::default()
177 .borders(Borders::ALL)
178 .border_style(Style::default().fg(theme::BLUE))
179 .title(Span::styled(" Help ", Style::default().fg(theme::TEXT)))
180 .style(Style::default().bg(theme::BASE));
181
182 let paragraph = Paragraph::new(lines)
183 .block(block)
184 .wrap(Wrap { trim: false });
185
186 frame.render_widget(paragraph, help_area);
187}