1use crate::app_state_container::AppStateContainer;
7use crate::buffer::{AppMode, Buffer, BufferAPI, BufferManager};
8use crate::ui::state::shadow_state::ShadowStateManager;
9use crate::ui::viewport_manager::ViewportManager;
10use crate::widgets::debug_widget::DebugWidget;
11use std::cell::RefCell;
12
13pub trait DebugContext {
16 fn buffer(&self) -> &dyn BufferAPI;
18 fn buffer_mut(&mut self) -> &mut dyn BufferAPI;
19 fn get_debug_widget(&self) -> &DebugWidget;
20 fn get_debug_widget_mut(&mut self) -> &mut DebugWidget;
21 fn get_shadow_state(&self) -> &RefCell<ShadowStateManager>;
22
23 fn get_buffer_manager(&self) -> &BufferManager;
25 fn get_viewport_manager(&self) -> &RefCell<Option<ViewportManager>>;
26 fn get_state_container(&self) -> &AppStateContainer;
27 fn get_state_container_mut(&mut self) -> &mut AppStateContainer;
28
29 fn get_navigation_timings(&self) -> &Vec<String>;
31 fn get_render_timings(&self) -> &Vec<String>;
32 fn debug_current_buffer(&mut self);
33 fn get_input_cursor(&self) -> usize;
34 fn get_visual_cursor(&self) -> (usize, usize);
35 fn get_input_text(&self) -> String;
36
37 fn toggle_debug_mode(&mut self) {
39 if self.buffer().get_mode() == AppMode::Debug {
41 self.set_mode_via_shadow_state(AppMode::Command, "debug_toggle_exit");
43 return;
44 }
45
46 let (
48 previous_mode,
49 last_query,
50 input_text,
51 selected_row,
52 current_column,
53 results_count,
54 filtered_count,
55 ) = self.collect_current_state();
56
57 self.set_mode_via_shadow_state(AppMode::Debug, "debug_toggle_enter");
59
60 self.debug_current_buffer();
62 let cursor_pos = self.get_input_cursor();
63 let visual_cursor = self.get_visual_cursor().1;
64 let query = self.get_input_text();
65
66 let query_for_parser = if previous_mode == AppMode::Results && !last_query.is_empty() {
68 last_query.clone()
69 } else if !query.is_empty() {
70 query.clone()
71 } else if !last_query.is_empty() {
72 last_query.clone()
73 } else {
74 query.clone()
75 };
76
77 let mut debug_info = self.debug_generate_parser_info(&query_for_parser);
79
80 debug_info.push_str(&self.debug_generate_buffer_state(
82 previous_mode,
83 &last_query,
84 &input_text,
85 cursor_pos,
86 visual_cursor,
87 ));
88
89 debug_info.push_str(&self.debug_generate_results_state(
91 results_count,
92 filtered_count,
93 selected_row,
94 current_column,
95 ));
96
97 debug_info.push_str(&self.debug_generate_datatable_schema());
99 debug_info.push_str(&self.debug_generate_dataview_state());
100
101 debug_info.push_str(&self.debug_generate_memory_info());
103
104 debug_info.push_str(&self.format_navigation_timing());
106
107 debug_info.push_str(&self.format_render_timing());
109
110 debug_info.push_str(&self.debug_generate_viewport_state());
112 debug_info.push_str(&self.debug_generate_navigation_state());
113
114 debug_info.push_str(&self.format_buffer_manager_state());
116
117 debug_info.push_str(&self.debug_generate_viewport_efficiency());
119
120 debug_info.push_str(&self.debug_generate_key_chord_info());
122
123 debug_info.push_str(&self.debug_generate_search_modes_info());
125
126 debug_info.push_str(&self.debug_generate_column_search_state());
128
129 debug_info.push_str(&self.debug_generate_trace_logs());
131
132 debug_info.push_str(&self.debug_generate_state_logs());
134
135 debug_info.push_str(&self.debug_generate_state_container_info());
137
138 debug_info.push_str("\n========== SHADOW STATE MANAGER ==========\n");
140 debug_info.push_str(&self.get_shadow_state().borrow().debug_info());
141 debug_info.push_str("\n==========================================\n");
142
143 self.get_debug_widget_mut().set_content(debug_info.clone());
145
146 match self
148 .get_state_container_mut()
149 .write_to_clipboard(&debug_info)
150 {
151 Ok(_) => {
152 let status_msg = format!(
153 "DEBUG INFO copied to clipboard ({} chars)!",
154 debug_info.len()
155 );
156 self.buffer_mut().set_status_message(status_msg);
157 }
158 Err(e) => {
159 let status_msg = format!("Clipboard error: {}", e);
160 self.buffer_mut().set_status_message(status_msg);
161 }
162 }
163 }
164
165 fn get_buffer_mut_if_available(&mut self) -> Option<&mut Buffer>;
167 fn set_mode_via_shadow_state(&mut self, mode: AppMode, trigger: &str);
168 fn collect_current_state(
169 &self,
170 ) -> (AppMode, String, String, Option<usize>, usize, usize, usize);
171 fn format_buffer_manager_state(&self) -> String;
172 fn debug_generate_viewport_efficiency(&self) -> String;
173 fn debug_generate_key_chord_info(&self) -> String;
174 fn debug_generate_search_modes_info(&self) -> String;
175 fn debug_generate_state_container_info(&self) -> String;
176
177 fn format_navigation_timing(&self) -> String {
179 let mut result = String::from("\n========== NAVIGATION TIMING ==========\n");
180 let timings = self.get_navigation_timings();
181 if !timings.is_empty() {
182 result.push_str(&format!("Last {} navigation timings:\n", timings.len()));
183 for timing in timings {
184 result.push_str(&format!(" {}\n", timing));
185 }
186 let total_ms: f64 = timings
188 .iter()
189 .filter_map(|s| self.debug_extract_timing(s))
190 .sum();
191 if timings.len() > 0 {
192 let avg_ms = total_ms / timings.len() as f64;
193 result.push_str(&format!("Average navigation time: {:.3}ms\n", avg_ms));
194 }
195 } else {
196 result.push_str("No navigation timing data yet (press j/k to navigate)\n");
197 }
198 result
199 }
200
201 fn format_render_timing(&self) -> String {
202 let mut result = String::from("\n========== RENDER TIMING ==========\n");
203 let timings = self.get_render_timings();
204 if !timings.is_empty() {
205 result.push_str(&format!("Last {} render timings:\n", timings.len()));
206 for timing in timings {
207 result.push_str(&format!(" {}\n", timing));
208 }
209 let total_ms: f64 = timings
211 .iter()
212 .filter_map(|s| self.debug_extract_timing(s))
213 .sum();
214 if timings.len() > 0 {
215 let avg_ms = total_ms / timings.len() as f64;
216 result.push_str(&format!("Average render time: {:.3}ms\n", avg_ms));
217 }
218 } else {
219 result.push_str("No render timing data yet\n");
220 }
221 result
222 }
223
224 fn debug_extract_timing(&self, s: &str) -> Option<f64> {
225 if let Some(ms_pos) = s.find("ms") {
227 let start = s[..ms_pos].rfind(' ').map(|p| p + 1).unwrap_or(0);
228 s[start..ms_pos].parse().ok()
229 } else {
230 None
231 }
232 }
233
234 fn collect_debug_info(&self) -> String;
236
237 fn debug_generate_memory_info(&self) -> String {
241 let mut output = String::from("\n========== MEMORY USAGE ==========\n");
242
243 let current_mb = crate::utils::memory_tracker::get_memory_mb();
245 output.push_str(&format!("Current Memory: {} MB\n", current_mb));
246
247 let buffer = self.buffer();
249 let audits = crate::utils::memory_audit::perform_memory_audit(
250 buffer.get_datatable(),
251 buffer.get_original_source(),
252 buffer.get_dataview(),
253 );
254
255 output.push_str("\nMemory Breakdown:\n");
256 for audit in &audits {
257 output.push_str(&format!(
258 " {}: {:.2} MB - {}\n",
259 audit.component,
260 audit.mb(),
261 audit.description
262 ));
263 }
264
265 if audits.iter().any(|a| a.component.contains("DUPLICATE")) {
267 output.push_str("\n⚠️ WARNING: Memory duplication detected!\n");
268 output.push_str(" DataTable is being stored twice (datatable + original_source)\n");
269 output.push_str(" Consider using Arc<DataTable> to share data\n");
270 }
271
272 output.push_str(&format!(
274 "\nMemory History:\n{}",
275 crate::utils::memory_tracker::format_memory_history()
276 ));
277
278 output
279 }
280
281 fn debug_generate_buffer_state(
285 &self,
286 mode: AppMode,
287 last_query: &str,
288 input_text: &str,
289 cursor_pos: usize,
290 visual_cursor: usize,
291 ) -> String {
292 format!(
293 "\n========== BUFFER STATE ==========\n\
294 Current Mode: {:?}\n\
295 Last Executed Query: '{}'\n\
296 Input Text: '{}'\n\
297 Input Cursor: {}\n\
298 Visual Cursor: {}\n",
299 mode, last_query, input_text, cursor_pos, visual_cursor
300 )
301 }
302
303 fn debug_generate_results_state(
304 &self,
305 results_count: usize,
306 filtered_count: usize,
307 selected_row: Option<usize>,
308 current_column: usize,
309 ) -> String {
310 format!(
311 "\n========== RESULTS STATE ==========\n\
312 Total Results: {}\n\
313 Filtered Results: {}\n\
314 Selected Row: {:?}\n\
315 Current Column: {}\n",
316 results_count, filtered_count, selected_row, current_column
317 )
318 }
319
320 fn debug_generate_viewport_state(&self) -> String {
322 let mut debug_info = String::new();
323 if let Some(buffer) = self.get_buffer_manager().current() {
324 debug_info.push_str("\n========== VIEWPORT STATE ==========\n");
325 let (scroll_row, scroll_col) = buffer.get_scroll_offset();
326 debug_info.push_str(&format!(
327 "Scroll Offset: row={}, col={}\n",
328 scroll_row, scroll_col
329 ));
330 debug_info.push_str(&format!(
331 "Current Column: {}\n",
332 buffer.get_current_column()
333 ));
334 debug_info.push_str(&format!("Selected Row: {:?}\n", buffer.get_selected_row()));
335 debug_info.push_str(&format!("Viewport Lock: {}\n", buffer.is_viewport_lock()));
336 if let Some(lock_row) = buffer.get_viewport_lock_row() {
337 debug_info.push_str(&format!("Viewport Lock Row: {}\n", lock_row));
338 }
339
340 if let Some(ref viewport_manager) = *self.get_viewport_manager().borrow() {
342 let visual_row = viewport_manager.get_crosshair_row();
343 let visual_col = viewport_manager.get_crosshair_col();
344 debug_info.push_str(&format!(
345 "ViewportManager Crosshair (visual): row={}, col={}\n",
346 visual_row, visual_col
347 ));
348
349 if let Some((viewport_row, viewport_col)) =
351 viewport_manager.get_crosshair_viewport_position()
352 {
353 debug_info.push_str(&format!(
354 "Crosshair in viewport (relative): row={}, col={}\n",
355 viewport_row, viewport_col
356 ));
357 }
358 }
359
360 if let Some(dataview) = buffer.get_dataview() {
362 let total_rows = dataview.row_count();
363 let total_cols = dataview.column_count();
364 let visible_rows = buffer.get_last_visible_rows();
365 debug_info.push_str(&format!("\nVisible Area:\n"));
366 debug_info.push_str(&format!(
367 " Total Data: {} rows × {} columns\n",
368 total_rows, total_cols
369 ));
370 debug_info.push_str(&format!(" Visible Rows in Terminal: {}\n", visible_rows));
371
372 if total_rows > 0 && visible_rows > 0 {
374 let start_row = scroll_row.min(total_rows.saturating_sub(1));
375 let end_row = (scroll_row + visible_rows).min(total_rows);
376 let percent_start = (start_row as f64 / total_rows as f64 * 100.0) as u32;
377 let percent_end = (end_row as f64 / total_rows as f64 * 100.0) as u32;
378 debug_info.push_str(&format!(
379 " Viewing rows {}-{} ({}%-{}% of data)\n",
380 start_row + 1,
381 end_row,
382 percent_start,
383 percent_end
384 ));
385 }
386
387 if total_cols > 0 {
388 let visible_cols_estimate = 10; let start_col = scroll_col.min(total_cols.saturating_sub(1));
390 let end_col = (scroll_col + visible_cols_estimate).min(total_cols);
391 debug_info.push_str(&format!(
392 " Viewing columns {}-{} of {}\n",
393 start_col + 1,
394 end_col,
395 total_cols
396 ));
397 }
398 }
399 }
400 debug_info
401 }
402
403 fn debug_generate_dataview_state(&self) -> String {
405 let mut debug_info = String::new();
406 if let Some(buffer) = self.get_buffer_manager().current() {
407 if let Some(dataview) = buffer.get_dataview() {
408 debug_info.push_str("\n========== DATAVIEW STATE ==========\n");
409
410 debug_info.push_str(&dataview.get_column_debug_info());
412 debug_info.push_str("\n");
413
414 let visible_columns = dataview.column_names();
416 let column_mappings = dataview.get_column_index_mapping();
417 debug_info.push_str(&format!(
418 "Visible Columns ({}) with Index Mapping:\n",
419 visible_columns.len()
420 ));
421 for (visible_idx, col_name, datatable_idx) in &column_mappings {
422 debug_info.push_str(&format!(
423 " V[{:3}] → DT[{:3}] : {}\n",
424 visible_idx, datatable_idx, col_name
425 ));
426 }
427
428 debug_info.push_str(&format!("\nVisible Rows: {}\n", dataview.row_count()));
430
431 debug_info.push_str("\n--- Internal State ---\n");
433
434 let visible_indices = dataview.get_visible_column_indices();
436 debug_info.push_str(&format!("visible_columns array: {:?}\n", visible_indices));
437
438 let pinned_names = dataview.get_pinned_column_names();
440 if !pinned_names.is_empty() {
441 debug_info.push_str(&format!("Pinned Columns ({}):\n", pinned_names.len()));
442 for (idx, name) in pinned_names.iter().enumerate() {
443 let source_idx = dataview.source().get_column_index(name).unwrap_or(999);
445 debug_info.push_str(&format!(
446 " [{}] {} (source_idx: {})\n",
447 idx, name, source_idx
448 ));
449 }
450 } else {
451 debug_info.push_str("Pinned Columns: None\n");
452 }
453
454 let sort_state = dataview.get_sort_state();
456 match sort_state.order {
457 crate::data::data_view::SortOrder::None => {
458 debug_info.push_str("Sort State: None\n");
459 }
460 crate::data::data_view::SortOrder::Ascending => {
461 if let Some(col_idx) = sort_state.column {
462 let col_name = visible_columns
463 .get(col_idx)
464 .map(|s| s.as_str())
465 .unwrap_or("unknown");
466 debug_info.push_str(&format!(
467 "Sort State: Ascending on column '{}' (idx: {})\n",
468 col_name, col_idx
469 ));
470 }
471 }
472 crate::data::data_view::SortOrder::Descending => {
473 if let Some(col_idx) = sort_state.column {
474 let col_name = visible_columns
475 .get(col_idx)
476 .map(|s| s.as_str())
477 .unwrap_or("unknown");
478 debug_info.push_str(&format!(
479 "Sort State: Descending on column '{}' (idx: {})\n",
480 col_name, col_idx
481 ));
482 }
483 }
484 }
485 }
486 }
487 debug_info
488 }
489
490 fn debug_generate_datatable_schema(&self) -> String {
492 let mut debug_info = String::new();
493 if let Some(buffer) = self.get_buffer_manager().current() {
494 if let Some(dataview) = buffer.get_dataview() {
495 let datatable = dataview.source();
496 debug_info.push_str("\n========== DATATABLE SCHEMA ==========\n");
497 debug_info.push_str(&datatable.get_schema_summary());
498 }
499 }
500 debug_info
501 }
502
503 fn render_debug(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
505 self.get_debug_widget().render(f, area, AppMode::Debug);
506 }
507
508 fn render_pretty_query(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
509 self.get_debug_widget()
510 .render(f, area, AppMode::PrettyQuery);
511 }
512
513 fn debug_generate_parser_info(&self, query: &str) -> String;
515 fn debug_generate_navigation_state(&self) -> String;
516 fn debug_generate_column_search_state(&self) -> String;
517 fn debug_generate_trace_logs(&self) -> String;
518 fn debug_generate_state_logs(&self) -> String;
519}