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 pub fn new() -> Self {
26 Self {
27 content: String::new(),
28 scroll_offset: 0,
29 max_scroll: 0,
30 }
31 }
32
33 pub fn generate_debug(
35 &mut self,
36 buffer: &dyn BufferAPI,
37 buffer_count: usize,
38 buffer_index: usize,
39 buffer_names: Vec<String>,
40 hybrid_parser: &HybridParser,
41 sort_state: &SortState,
42 input_text: &str,
43 cursor_pos: usize,
44 visual_cursor: usize,
45 api_url: &str,
46 ) {
47 let mut debug_info = DebugInfo::generate_full_debug_simple(
49 buffer,
50 buffer_count,
51 buffer_index,
52 buffer_names,
53 hybrid_parser,
54 sort_state,
55 input_text,
56 cursor_pos,
57 visual_cursor,
58 api_url,
59 );
60
61 if input_text.to_lowercase().contains(" where ") {
63 let where_ast_info = match Self::parse_where_clause_ast(input_text) {
64 Ok(ast_str) => ast_str,
65 Err(e) => format!(
66 "\n========== WHERE CLAUSE AST ==========\nError parsing WHERE clause: {}\n",
67 e
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 pub fn get_visible_lines(&self, height: usize) -> Vec<Line<'static>> {
189 let lines: Vec<&str> = self.content.lines().collect();
190 let start = self.scroll_offset as usize;
191 let end = (start + height).min(lines.len());
192
193 lines[start..end]
194 .iter()
195 .map(|line| Line::from(line.to_string()))
196 .collect()
197 }
198
199 pub fn scroll_up(&mut self, amount: u16) {
201 self.scroll_offset = self.scroll_offset.saturating_sub(amount);
202 }
203
204 pub fn scroll_down(&mut self, amount: u16) {
206 self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
207 }
208
209 pub fn scroll_to_top(&mut self) {
211 self.scroll_offset = 0;
212 }
213
214 pub fn scroll_to_bottom(&mut self) {
216 self.scroll_offset = self.max_scroll;
217 }
218
219 fn update_max_scroll(&mut self) {
221 let line_count = self.content.lines().count() as u16;
222 self.max_scroll = line_count.saturating_sub(10); }
224
225 pub fn get_content(&self) -> &str {
227 &self.content
228 }
229
230 pub fn set_content(&mut self, content: String) {
232 self.content = content;
233 self.scroll_offset = 0;
234 self.update_max_scroll();
235 }
236
237 fn parse_where_clause_ast(query: &str) -> Result<String, String> {
239 let lower_query = query.to_lowercase();
241 let where_pos = lower_query.find(" where ");
242
243 if let Some(pos) = where_pos {
244 let where_start = pos + 7; let where_clause = &query[where_start..];
246
247 let end_keywords = ["order by", "group by", "limit", "offset", ";"];
249 let mut where_end = where_clause.len();
250
251 for keyword in &end_keywords {
252 if let Some(keyword_pos) = where_clause.to_lowercase().find(keyword) {
253 where_end = where_end.min(keyword_pos);
254 }
255 }
256
257 let where_only = where_clause[..where_end].trim();
258
259 match where_parser::WhereParser::parse(where_only) {
260 Ok(ast) => {
261 let mut result = String::from("\n========== WHERE CLAUSE AST ==========\n");
262 result.push_str(&format!("Input: {}\n", where_only));
263 result.push_str(&format!("Parsed AST:\n{:#?}\n", ast));
264 Ok(result)
265 }
266 Err(e) => Err(format!("Failed to parse WHERE clause: {}", e)),
267 }
268 } else {
269 Err("No WHERE clause found in query".to_string())
270 }
271 }
272}
273
274impl Default for DebugWidget {
275 fn default() -> Self {
276 Self::new()
277 }
278}