Skip to main content

sql_cli/widgets/
debug_widget.rs

1use 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
13/// A self-contained debug widget that manages its own state and rendering
14pub struct DebugWidget {
15    /// The debug content to display
16    content: String,
17    /// Current scroll offset
18    scroll_offset: u16,
19    /// Maximum scroll position
20    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    /// Generate and set debug content
34    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        // Generate full debug info
48        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    /// Generate pretty formatted SQL
67    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    /// Generate test case content
81    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    /// Handle key events for the debug widget
88    pub fn handle_key(&mut self, key: KeyEvent) -> bool {
89        match key.code {
90            // Navigation
91            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            // Exit debug mode
117            KeyCode::Esc | KeyCode::Char('q') => true,
118
119            // Ctrl+C to copy debug content to clipboard
120            KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
121                // This would return true to signal the main app to copy content
122                // The main app would handle the actual clipboard operation
123                true
124            }
125
126            _ => false,
127        }
128    }
129
130    /// Render the debug widget
131    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        // Check if there's a parse error
141        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    /// Get the visible lines based on scroll offset
176    #[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    /// Scroll up by the specified amount
189    pub fn scroll_up(&mut self, amount: u16) {
190        self.scroll_offset = self.scroll_offset.saturating_sub(amount);
191    }
192
193    /// Scroll down by the specified amount
194    pub fn scroll_down(&mut self, amount: u16) {
195        self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
196    }
197
198    /// Scroll to the top
199    pub fn scroll_to_top(&mut self) {
200        self.scroll_offset = 0;
201    }
202
203    /// Scroll to the bottom
204    pub fn scroll_to_bottom(&mut self) {
205        self.scroll_offset = self.max_scroll;
206    }
207
208    /// Update the maximum scroll position based on content
209    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); // Leave some visible lines
212    }
213
214    /// Get the current content (for clipboard operations)
215    #[must_use]
216    pub fn get_content(&self) -> &str {
217        &self.content
218    }
219
220    /// Set custom content
221    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}