sql_cli/widgets/
debug_widget.rs1use crate::buffer::{AppMode, BufferAPI, SortState};
2use crate::debug_info::DebugInfo;
3use crate::hybrid_parser::HybridParser;
4use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
5use ratatui::{
6 layout::Rect,
7 style::{Color, Style},
8 text::{Line, Text},
9 widgets::{Block, Borders, Paragraph, Wrap},
10 Frame,
11};
12
13pub struct DebugWidget {
15 content: String,
17 scroll_offset: u16,
19 max_scroll: u16,
21}
22
23impl DebugWidget {
24 #[must_use]
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 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 self.content = debug_info;
62 self.scroll_offset = 0;
63 self.update_max_scroll();
64 }
65
66 pub fn generate_pretty_sql(&mut self, query: &str) {
68 if !query.trim().is_empty() {
69 let debug_text = format!(
70 "Pretty SQL Query\n{}\n\n{}",
71 "=".repeat(50),
72 crate::recursive_parser::format_sql_pretty_compact(query, 5).join("\n")
73 );
74 self.content = debug_text;
75 self.scroll_offset = 0;
76 self.update_max_scroll();
77 }
78 }
79
80 pub fn generate_test_case(&mut self, buffer: &dyn BufferAPI) {
82 self.content = DebugInfo::generate_test_case(buffer);
83 self.scroll_offset = 0;
84 self.update_max_scroll();
85 }
86
87 pub fn handle_key(&mut self, key: KeyEvent) -> bool {
89 match key.code {
90 KeyCode::Up | KeyCode::Char('k') => {
92 self.scroll_up(1);
93 false
94 }
95 KeyCode::Down | KeyCode::Char('j') => {
96 self.scroll_down(1);
97 false
98 }
99 KeyCode::PageUp => {
100 self.scroll_up(10);
101 false
102 }
103 KeyCode::PageDown => {
104 self.scroll_down(10);
105 false
106 }
107 KeyCode::Home | KeyCode::Char('g') => {
108 self.scroll_to_top();
109 false
110 }
111 KeyCode::End | KeyCode::Char('G') => {
112 self.scroll_to_bottom();
113 false
114 }
115
116 KeyCode::Esc | KeyCode::Char('q') => true,
118
119 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
121 true
124 }
125
126 _ => false,
127 }
128 }
129
130 pub fn render(&self, f: &mut Frame, area: Rect, mode: AppMode) {
132 let visible_height = area.height.saturating_sub(2) as usize;
133 let visible_lines = self.get_visible_lines(visible_height);
134
135 let debug_text = Text::from(visible_lines);
136 let total_lines = self.content.lines().count();
137 let start = self.scroll_offset as usize;
138 let end = (start + visible_height).min(total_lines);
139
140 let has_parse_error = self.content.contains("❌ PARSE ERROR ❌");
142 let (border_color, title_prefix) = if has_parse_error {
143 (Color::Red, "⚠️ Parser Debug Info [PARSE ERROR] ")
144 } else {
145 (Color::Yellow, "Parser Debug Info ")
146 };
147
148 let title = match mode {
149 AppMode::Debug => format!(
150 "{}- Lines {}-{} of {} (↑↓/jk: scroll, PgUp/PgDn: page, Home/g: top, End/G: bottom, q/Esc: exit)",
151 title_prefix,
152 start + 1,
153 end,
154 total_lines
155 ),
156 AppMode::PrettyQuery => {
157 "Pretty SQL Query (F6) - ↑↓ to scroll, Esc/q to close".to_string()
158 }
159 _ => "Debug Info".to_string(),
160 };
161
162 let debug_paragraph = Paragraph::new(debug_text)
163 .block(
164 Block::default()
165 .borders(Borders::ALL)
166 .title(title)
167 .border_style(Style::default().fg(border_color)),
168 )
169 .style(Style::default().fg(Color::White))
170 .wrap(Wrap { trim: false });
171
172 f.render_widget(debug_paragraph, area);
173 }
174
175 #[must_use]
177 pub fn get_visible_lines(&self, height: usize) -> Vec<Line<'static>> {
178 let lines: Vec<&str> = self.content.lines().collect();
179 let start = self.scroll_offset as usize;
180 let end = (start + height).min(lines.len());
181
182 lines[start..end]
183 .iter()
184 .map(|line| Line::from((*line).to_string()))
185 .collect()
186 }
187
188 pub fn scroll_up(&mut self, amount: u16) {
190 self.scroll_offset = self.scroll_offset.saturating_sub(amount);
191 }
192
193 pub fn scroll_down(&mut self, amount: u16) {
195 self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
196 }
197
198 pub fn scroll_to_top(&mut self) {
200 self.scroll_offset = 0;
201 }
202
203 pub fn scroll_to_bottom(&mut self) {
205 self.scroll_offset = self.max_scroll;
206 }
207
208 fn update_max_scroll(&mut self) {
210 let line_count = self.content.lines().count() as u16;
211 self.max_scroll = line_count.saturating_sub(10); }
213
214 #[must_use]
216 pub fn get_content(&self) -> &str {
217 &self.content
218 }
219
220 pub fn set_content(&mut self, content: String) {
222 self.content = content;
223 self.scroll_offset = 0;
224 self.update_max_scroll();
225 }
226}
227
228impl Default for DebugWidget {
229 fn default() -> Self {
230 Self::new()
231 }
232}