sql_cli/widgets/
debug_widget.rs1use crate::buffer::{AppMode, BufferAPI, SortState};
2use crate::debug_info::DebugInfo;
3use crate::hybrid_parser::HybridParser;
4use crate::where_parser;
5use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
6use ratatui::{
7 layout::Rect,
8 style::{Color, Style},
9 text::{Line, Text},
10 widgets::{Block, Borders, Paragraph, Wrap},
11 Frame,
12};
13
14pub struct DebugWidget {
16 content: String,
18 scroll_offset: u16,
20 max_scroll: u16,
22}
23
24impl DebugWidget {
25 #[must_use]
26 pub fn new() -> Self {
27 Self {
28 content: String::new(),
29 scroll_offset: 0,
30 max_scroll: 0,
31 }
32 }
33
34 pub fn generate_debug(
36 &mut self,
37 buffer: &dyn BufferAPI,
38 buffer_count: usize,
39 buffer_index: usize,
40 buffer_names: Vec<String>,
41 hybrid_parser: &HybridParser,
42 sort_state: &SortState,
43 input_text: &str,
44 cursor_pos: usize,
45 visual_cursor: usize,
46 api_url: &str,
47 ) {
48 let mut debug_info = DebugInfo::generate_full_debug_simple(
50 buffer,
51 buffer_count,
52 buffer_index,
53 buffer_names,
54 hybrid_parser,
55 sort_state,
56 input_text,
57 cursor_pos,
58 visual_cursor,
59 api_url,
60 );
61
62 if input_text.to_lowercase().contains(" where ") {
64 let where_ast_info = match Self::parse_where_clause_ast(input_text) {
65 Ok(ast_str) => ast_str,
66 Err(e) => format!(
67 "\n========== WHERE CLAUSE AST ==========\nError parsing WHERE clause: {e}\n"
68 ),
69 };
70 debug_info.push_str(&where_ast_info);
71 }
72
73 self.content = debug_info;
74 self.scroll_offset = 0;
75 self.update_max_scroll();
76 }
77
78 pub fn generate_pretty_sql(&mut self, query: &str) {
80 if !query.trim().is_empty() {
81 let debug_text = format!(
82 "Pretty SQL Query\n{}\n\n{}",
83 "=".repeat(50),
84 crate::recursive_parser::format_sql_pretty_compact(query, 5).join("\n")
85 );
86 self.content = debug_text;
87 self.scroll_offset = 0;
88 self.update_max_scroll();
89 }
90 }
91
92 pub fn generate_test_case(&mut self, buffer: &dyn BufferAPI) {
94 self.content = DebugInfo::generate_test_case(buffer);
95 self.scroll_offset = 0;
96 self.update_max_scroll();
97 }
98
99 pub fn handle_key(&mut self, key: KeyEvent) -> bool {
101 match key.code {
102 KeyCode::Up | KeyCode::Char('k') => {
104 self.scroll_up(1);
105 false
106 }
107 KeyCode::Down | KeyCode::Char('j') => {
108 self.scroll_down(1);
109 false
110 }
111 KeyCode::PageUp => {
112 self.scroll_up(10);
113 false
114 }
115 KeyCode::PageDown => {
116 self.scroll_down(10);
117 false
118 }
119 KeyCode::Home | KeyCode::Char('g') => {
120 self.scroll_to_top();
121 false
122 }
123 KeyCode::End | KeyCode::Char('G') => {
124 self.scroll_to_bottom();
125 false
126 }
127
128 KeyCode::Esc | KeyCode::Char('q') => true,
130
131 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
133 true
136 }
137
138 _ => false,
139 }
140 }
141
142 pub fn render(&self, f: &mut Frame, area: Rect, mode: AppMode) {
144 let visible_height = area.height.saturating_sub(2) as usize;
145 let visible_lines = self.get_visible_lines(visible_height);
146
147 let debug_text = Text::from(visible_lines);
148 let total_lines = self.content.lines().count();
149 let start = self.scroll_offset as usize;
150 let end = (start + visible_height).min(total_lines);
151
152 let has_parse_error = self.content.contains("❌ PARSE ERROR ❌");
154 let (border_color, title_prefix) = if has_parse_error {
155 (Color::Red, "⚠️ Parser Debug Info [PARSE ERROR] ")
156 } else {
157 (Color::Yellow, "Parser Debug Info ")
158 };
159
160 let title = match mode {
161 AppMode::Debug => format!(
162 "{}- Lines {}-{} of {} (↑↓/jk: scroll, PgUp/PgDn: page, Home/g: top, End/G: bottom, q/Esc: exit)",
163 title_prefix,
164 start + 1,
165 end,
166 total_lines
167 ),
168 AppMode::PrettyQuery => {
169 "Pretty SQL Query (F6) - ↑↓ to scroll, Esc/q to close".to_string()
170 }
171 _ => "Debug Info".to_string(),
172 };
173
174 let debug_paragraph = Paragraph::new(debug_text)
175 .block(
176 Block::default()
177 .borders(Borders::ALL)
178 .title(title)
179 .border_style(Style::default().fg(border_color)),
180 )
181 .style(Style::default().fg(Color::White))
182 .wrap(Wrap { trim: false });
183
184 f.render_widget(debug_paragraph, area);
185 }
186
187 #[must_use]
189 pub fn get_visible_lines(&self, height: usize) -> Vec<Line<'static>> {
190 let lines: Vec<&str> = self.content.lines().collect();
191 let start = self.scroll_offset as usize;
192 let end = (start + height).min(lines.len());
193
194 lines[start..end]
195 .iter()
196 .map(|line| Line::from((*line).to_string()))
197 .collect()
198 }
199
200 pub fn scroll_up(&mut self, amount: u16) {
202 self.scroll_offset = self.scroll_offset.saturating_sub(amount);
203 }
204
205 pub fn scroll_down(&mut self, amount: u16) {
207 self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
208 }
209
210 pub fn scroll_to_top(&mut self) {
212 self.scroll_offset = 0;
213 }
214
215 pub fn scroll_to_bottom(&mut self) {
217 self.scroll_offset = self.max_scroll;
218 }
219
220 fn update_max_scroll(&mut self) {
222 let line_count = self.content.lines().count() as u16;
223 self.max_scroll = line_count.saturating_sub(10); }
225
226 #[must_use]
228 pub fn get_content(&self) -> &str {
229 &self.content
230 }
231
232 pub fn set_content(&mut self, content: String) {
234 self.content = content;
235 self.scroll_offset = 0;
236 self.update_max_scroll();
237 }
238
239 fn parse_where_clause_ast(query: &str) -> Result<String, String> {
241 let lower_query = query.to_lowercase();
243 let where_pos = lower_query.find(" where ");
244
245 if let Some(pos) = where_pos {
246 let where_start = pos + 7; let where_clause = &query[where_start..];
248
249 let end_keywords = ["order by", "group by", "limit", "offset", ";"];
251 let mut where_end = where_clause.len();
252
253 for keyword in &end_keywords {
254 if let Some(keyword_pos) = where_clause.to_lowercase().find(keyword) {
255 where_end = where_end.min(keyword_pos);
256 }
257 }
258
259 let where_only = where_clause[..where_end].trim();
260
261 match where_parser::WhereParser::parse(where_only) {
262 Ok(ast) => {
263 let mut result = String::from("\n========== WHERE CLAUSE AST ==========\n");
264 result.push_str(&format!("Input: {where_only}\n"));
265 result.push_str(&format!("Parsed AST:\n{ast:#?}\n"));
266 Ok(result)
267 }
268 Err(e) => Err(format!("Failed to parse WHERE clause: {e}")),
269 }
270 } else {
271 Err("No WHERE clause found in query".to_string())
272 }
273 }
274}
275
276impl Default for DebugWidget {
277 fn default() -> Self {
278 Self::new()
279 }
280}