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("No navigation timing data yet (press j/k to navigate)\n");
183 } else {
184 result.push_str(&format!("Last {} navigation timings:\n", timings.len()));
185 for timing in timings {
186 result.push_str(&format!(" {timing}\n"));
187 }
188 let total_ms: f64 = timings
190 .iter()
191 .filter_map(|s| self.debug_extract_timing(s))
192 .sum();
193 if !timings.is_empty() {
194 let avg_ms = total_ms / timings.len() as f64;
195 result.push_str(&format!("Average navigation time: {avg_ms:.3}ms\n"));
196 }
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("No render timing data yet\n");
206 } else {
207 result.push_str(&format!("Last {} render timings:\n", timings.len()));
208 for timing in timings {
209 result.push_str(&format!(" {timing}\n"));
210 }
211 let total_ms: f64 = timings
213 .iter()
214 .filter_map(|s| self.debug_extract_timing(s))
215 .sum();
216 if !timings.is_empty() {
217 let avg_ms = total_ms / timings.len() as f64;
218 result.push_str(&format!("Average render time: {avg_ms:.3}ms\n"));
219 }
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_or(0, |p| p + 1);
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: {current_mb} MB\n"));
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: {mode:?}\n\
295 Last Executed Query: '{last_query}'\n\
296 Input Text: '{input_text}'\n\
297 Input Cursor: {cursor_pos}\n\
298 Visual Cursor: {visual_cursor}\n"
299 )
300 }
301
302 fn debug_generate_results_state(
303 &self,
304 results_count: usize,
305 filtered_count: usize,
306 selected_row: Option<usize>,
307 current_column: usize,
308 ) -> String {
309 format!(
310 "\n========== RESULTS STATE ==========\n\
311 Total Results: {results_count}\n\
312 Filtered Results: {filtered_count}\n\
313 Selected Row: {selected_row:?}\n\
314 Current Column: {current_column}\n"
315 )
316 }
317
318 fn debug_generate_viewport_state(&self) -> String {
320 let mut debug_info = String::new();
321 if let Some(buffer) = self.get_buffer_manager().current() {
322 debug_info.push_str("\n========== VIEWPORT STATE ==========\n");
323 let (scroll_row, scroll_col) = buffer.get_scroll_offset();
324 debug_info.push_str(&format!(
325 "Scroll Offset: row={scroll_row}, col={scroll_col}\n"
326 ));
327 debug_info.push_str(&format!(
328 "Current Column: {}\n",
329 buffer.get_current_column()
330 ));
331 debug_info.push_str(&format!("Selected Row: {:?}\n", buffer.get_selected_row()));
332 debug_info.push_str(&format!("Viewport Lock: {}\n", buffer.is_viewport_lock()));
333 if let Some(lock_row) = buffer.get_viewport_lock_row() {
334 debug_info.push_str(&format!("Viewport Lock Row: {lock_row}\n"));
335 }
336
337 if let Some(ref viewport_manager) = *self.get_viewport_manager().borrow() {
339 let visual_row = viewport_manager.get_crosshair_row();
340 let visual_col = viewport_manager.get_crosshair_col();
341 debug_info.push_str(&format!(
342 "ViewportManager Crosshair (visual): row={visual_row}, col={visual_col}\n"
343 ));
344
345 if let Some((viewport_row, viewport_col)) =
347 viewport_manager.get_crosshair_viewport_position()
348 {
349 debug_info.push_str(&format!(
350 "Crosshair in viewport (relative): row={viewport_row}, col={viewport_col}\n"
351 ));
352 }
353 }
354
355 if let Some(dataview) = buffer.get_dataview() {
357 let total_rows = dataview.row_count();
358 let total_cols = dataview.column_count();
359 let visible_rows = buffer.get_last_visible_rows();
360 debug_info.push_str("\nVisible Area:\n");
361 debug_info.push_str(&format!(
362 " Total Data: {total_rows} rows × {total_cols} columns\n"
363 ));
364 debug_info.push_str(&format!(" Visible Rows in Terminal: {visible_rows}\n"));
365
366 if total_rows > 0 && visible_rows > 0 {
368 let start_row = scroll_row.min(total_rows.saturating_sub(1));
369 let end_row = (scroll_row + visible_rows).min(total_rows);
370 let percent_start = (start_row as f64 / total_rows as f64 * 100.0) as u32;
371 let percent_end = (end_row as f64 / total_rows as f64 * 100.0) as u32;
372 debug_info.push_str(&format!(
373 " Viewing rows {}-{} ({}%-{}% of data)\n",
374 start_row + 1,
375 end_row,
376 percent_start,
377 percent_end
378 ));
379 }
380
381 if total_cols > 0 {
382 let visible_cols_estimate = 10; let start_col = scroll_col.min(total_cols.saturating_sub(1));
384 let end_col = (scroll_col + visible_cols_estimate).min(total_cols);
385 debug_info.push_str(&format!(
386 " Viewing columns {}-{} of {}\n",
387 start_col + 1,
388 end_col,
389 total_cols
390 ));
391 }
392 }
393 }
394 debug_info
395 }
396
397 fn debug_generate_dataview_state(&self) -> String {
399 let mut debug_info = String::new();
400 if let Some(buffer) = self.get_buffer_manager().current() {
401 if let Some(dataview) = buffer.get_dataview() {
402 debug_info.push_str("\n========== DATAVIEW STATE ==========\n");
403
404 debug_info.push_str(&dataview.get_column_debug_info());
406 debug_info.push('\n');
407
408 let visible_columns = dataview.column_names();
410 let column_mappings = dataview.get_column_index_mapping();
411 debug_info.push_str(&format!(
412 "Visible Columns ({}) with Index Mapping:\n",
413 visible_columns.len()
414 ));
415 for (visible_idx, col_name, datatable_idx) in &column_mappings {
416 debug_info.push_str(&format!(
417 " V[{visible_idx:3}] → DT[{datatable_idx:3}] : {col_name}\n"
418 ));
419 }
420
421 debug_info.push_str(&format!("\nVisible Rows: {}\n", dataview.row_count()));
423
424 debug_info.push_str("\n--- Internal State ---\n");
426
427 let visible_indices = dataview.get_visible_column_indices();
429 debug_info.push_str(&format!("visible_columns array: {visible_indices:?}\n"));
430
431 let pinned_names = dataview.get_pinned_column_names();
433 if pinned_names.is_empty() {
434 debug_info.push_str("Pinned Columns: None\n");
435 } else {
436 debug_info.push_str(&format!("Pinned Columns ({}):\n", pinned_names.len()));
437 for (idx, name) in pinned_names.iter().enumerate() {
438 let source_idx = dataview.source().get_column_index(name).unwrap_or(999);
440 debug_info
441 .push_str(&format!(" [{idx}] {name} (source_idx: {source_idx})\n"));
442 }
443 }
444
445 let sort_state = dataview.get_sort_state();
447 match sort_state.order {
448 crate::data::data_view::SortOrder::None => {
449 debug_info.push_str("Sort State: None\n");
450 }
451 crate::data::data_view::SortOrder::Ascending => {
452 if let Some(col_idx) = sort_state.column {
453 let col_name = visible_columns
454 .get(col_idx)
455 .map_or("unknown", std::string::String::as_str);
456 debug_info.push_str(&format!(
457 "Sort State: Ascending on column '{col_name}' (idx: {col_idx})\n"
458 ));
459 }
460 }
461 crate::data::data_view::SortOrder::Descending => {
462 if let Some(col_idx) = sort_state.column {
463 let col_name = visible_columns
464 .get(col_idx)
465 .map_or("unknown", std::string::String::as_str);
466 debug_info.push_str(&format!(
467 "Sort State: Descending on column '{col_name}' (idx: {col_idx})\n"
468 ));
469 }
470 }
471 }
472 }
473 }
474 debug_info
475 }
476
477 fn debug_generate_datatable_schema(&self) -> String {
479 let mut debug_info = String::new();
480 if let Some(buffer) = self.get_buffer_manager().current() {
481 if let Some(dataview) = buffer.get_dataview() {
482 let datatable = dataview.source();
483 debug_info.push_str("\n========== DATATABLE SCHEMA ==========\n");
484 debug_info.push_str(&datatable.get_schema_summary());
485 }
486 }
487 debug_info
488 }
489
490 fn render_debug(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
492 self.get_debug_widget().render(f, area, AppMode::Debug);
493 }
494
495 fn render_pretty_query(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
496 self.get_debug_widget()
497 .render(f, area, AppMode::PrettyQuery);
498 }
499
500 fn debug_generate_parser_info(&self, query: &str) -> String;
502 fn debug_generate_navigation_state(&self) -> String;
503 fn debug_generate_column_search_state(&self) -> String;
504 fn debug_generate_trace_logs(&self) -> String;
505 fn debug_generate_state_logs(&self) -> String;
506}