Skip to main content

sql_cli/
buffer.rs

1use crate::api_client::QueryResponse; // V50: Still needed for conversion from API responses
2use crate::csv_datasource::CsvApiClient; // Kept for API compatibility in from_csv/from_json
3use crate::cursor_operations::CursorOperations;
4use crate::data::data_view::DataView;
5use crate::data::datatable::DataTable;
6use crate::hybrid_parser::HybridParser;
7use crate::input_manager::{create_from_input, create_single_line, InputManager};
8use anyhow::Result;
9use crossterm::event::KeyEvent;
10use fuzzy_matcher::skim::SkimMatcherV2;
11use ratatui::style::Color;
12use ratatui::widgets::TableState;
13use regex::Regex;
14use std::collections::BTreeMap;
15use std::path::PathBuf;
16use std::sync::Arc;
17use tracing::debug;
18use tui_input::Input;
19
20// Re-define the types we need (these should eventually be moved to a common module)
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub enum AppMode {
23    Command,
24    Results,
25    Search,
26    Filter,
27    FuzzyFilter,
28    ColumnSearch,
29    Help,
30    History,
31    Debug,
32    PrettyQuery,
33    JumpToRow,
34    ColumnStats,
35}
36
37#[derive(Clone, PartialEq, Debug)]
38pub enum EditMode {
39    SingleLine,
40    MultiLine,
41}
42
43#[derive(Clone, PartialEq, Copy, Debug)]
44pub enum SortOrder {
45    Ascending,
46    Descending,
47    None,
48}
49
50#[derive(Clone)]
51pub struct SortState {
52    pub column: Option<usize>,
53    pub order: SortOrder,
54}
55
56#[derive(Clone, Debug, Default)]
57pub struct FilterState {
58    pub pattern: String,
59    pub regex: Option<Regex>,
60    pub active: bool,
61}
62
63#[derive(Default)]
64pub struct FuzzyFilterState {
65    pub pattern: String,
66    pub active: bool,
67    pub matcher: SkimMatcherV2,
68    pub filtered_indices: Vec<usize>,
69}
70
71impl std::fmt::Debug for FuzzyFilterState {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("FuzzyFilterState")
74            .field("pattern", &self.pattern)
75            .field("active", &self.active)
76            .field("matcher", &"SkimMatcherV2")
77            .field("filtered_indices", &self.filtered_indices)
78            .finish()
79    }
80}
81
82impl Clone for FuzzyFilterState {
83    fn clone(&self) -> Self {
84        Self {
85            pattern: self.pattern.clone(),
86            active: self.active,
87            matcher: SkimMatcherV2::default(), // Create new matcher
88            filtered_indices: self.filtered_indices.clone(),
89        }
90    }
91}
92
93#[derive(Clone, Debug, Default)]
94pub struct SearchState {
95    pub pattern: String,
96    pub current_match: Option<(usize, usize)>,
97    pub matches: Vec<(usize, usize)>,
98    pub match_index: usize,
99}
100
101// ColumnSearchState: MIGRATED to AppStateContainer
102
103#[derive(Clone, Debug, PartialEq)]
104pub enum SelectionMode {
105    Row,
106    Cell,
107    Column,
108}
109
110/// `ViewState` consolidates all view-related state for a buffer
111/// This is the single source of truth for navigation, selection, and viewport state
112#[derive(Clone, Debug)]
113pub struct ViewState {
114    // Position
115    pub crosshair_row: usize,
116    pub crosshair_col: usize,
117    pub scroll_offset: (usize, usize),
118
119    // Selection
120    pub selection_mode: SelectionMode,
121    pub selected_cells: Vec<(usize, usize)>,
122    pub selection_anchor: Option<(usize, usize)>,
123
124    // Viewport config
125    pub viewport_lock: bool,
126    pub cursor_lock: bool,
127
128    // Navigation history
129    pub navigation_history: Vec<(usize, usize)>,
130    pub history_index: usize,
131
132    // Viewport dimensions (cached from last render)
133    pub viewport_rows: usize,
134    pub viewport_columns: usize,
135    pub total_rows: usize,
136    pub total_columns: usize,
137}
138
139impl Default for ViewState {
140    fn default() -> Self {
141        Self {
142            crosshair_row: 0,
143            crosshair_col: 0,
144            scroll_offset: (0, 0),
145            selection_mode: SelectionMode::Row,
146            selected_cells: Vec::new(),
147            selection_anchor: None,
148            viewport_lock: false,
149            cursor_lock: false,
150            navigation_history: Vec::new(),
151            history_index: 0,
152            viewport_rows: 0,
153            viewport_columns: 0,
154            total_rows: 0,
155            total_columns: 0,
156        }
157    }
158}
159
160#[derive(Clone, Debug)]
161pub enum ColumnType {
162    String,
163    Numeric,
164    Mixed,
165}
166
167#[derive(Clone)]
168pub struct ColumnStatistics {
169    pub column_name: String,
170    pub column_type: ColumnType,
171    // For all columns
172    pub total_count: usize,
173    pub null_count: usize,
174    pub unique_count: usize,
175    // For categorical/string columns
176    pub frequency_map: Option<BTreeMap<String, usize>>,
177    // For numeric columns
178    pub min: Option<f64>,
179    pub max: Option<f64>,
180    pub sum: Option<f64>,
181    pub mean: Option<f64>,
182    pub median: Option<f64>,
183}
184
185// ColumnSearchState Default impl: MIGRATED to AppStateContainer
186
187// pub type ColumnStatistics = std::collections::BTreeMap<String, String>; // Replaced with struct
188
189/// `BufferAPI` trait - defines the interface for interacting with buffer state
190/// This abstraction allows the TUI to work with buffer state without knowing
191/// the implementation details, enabling gradual migration and testing
192pub trait BufferAPI: Send + Sync {
193    // --- Identity ---
194    fn get_id(&self) -> usize;
195    // --- Query ---
196    fn get_query(&self) -> String;
197    fn set_query(&mut self, query: String);
198    // V50: Removed get_results/set_results - use DataTable methods instead
199    fn get_last_query(&self) -> String;
200    fn set_last_query(&mut self, query: String);
201
202    // --- V50: DataTable is primary storage ---
203    fn get_datatable(&self) -> Option<&DataTable>;
204    fn get_datatable_mut(&mut self) -> Option<&mut DataTable>;
205    fn has_datatable(&self) -> bool;
206    fn set_datatable(&mut self, datatable: Option<Arc<DataTable>>);
207    fn get_original_source(&self) -> Option<&DataTable>;
208    /// V50: Helper to convert `QueryResponse` to `DataTable` and store it
209    fn set_results_as_datatable(&mut self, response: Option<QueryResponse>) -> Result<(), String>;
210
211    // --- V51: DataView support (direct query results) ---
212    fn get_dataview(&self) -> Option<&DataView>;
213    fn get_dataview_mut(&mut self) -> Option<&mut DataView>;
214    fn set_dataview(&mut self, dataview: Option<DataView>);
215    fn has_dataview(&self) -> bool;
216
217    // --- Mode and Status ---
218    fn get_mode(&self) -> AppMode;
219    fn set_mode(&mut self, mode: AppMode);
220    fn get_edit_mode(&self) -> EditMode;
221    fn set_edit_mode(&mut self, mode: EditMode);
222    fn get_status_message(&self) -> String;
223    fn set_status_message(&mut self, message: String);
224
225    // --- Table Navigation ---
226    fn get_selected_row(&self) -> Option<usize>;
227    fn set_selected_row(&mut self, row: Option<usize>);
228    fn get_current_column(&self) -> usize;
229    fn set_current_column(&mut self, col: usize);
230    fn get_scroll_offset(&self) -> (usize, usize);
231    fn set_scroll_offset(&mut self, offset: (usize, usize));
232    fn get_last_results_row(&self) -> Option<usize>;
233    fn set_last_results_row(&mut self, row: Option<usize>);
234    fn get_last_scroll_offset(&self) -> (usize, usize);
235    fn set_last_scroll_offset(&mut self, offset: (usize, usize));
236
237    // --- Filtering ---
238    fn get_filter_pattern(&self) -> String;
239    fn set_filter_pattern(&mut self, pattern: String);
240    fn is_filter_active(&self) -> bool;
241    fn set_filter_active(&mut self, active: bool);
242    // REMOVED: get_filtered_data/set_filtered_data - DataView handles filtering
243
244    // --- Fuzzy Filter ---
245    fn get_fuzzy_filter_pattern(&self) -> String;
246    fn set_fuzzy_filter_pattern(&mut self, pattern: String);
247    fn is_fuzzy_filter_active(&self) -> bool;
248    fn set_fuzzy_filter_active(&mut self, active: bool);
249    fn get_fuzzy_filter_indices(&self) -> &Vec<usize>;
250    fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>);
251    fn clear_fuzzy_filter(&mut self);
252
253    // --- Search ---
254    fn get_search_pattern(&self) -> String;
255    fn set_search_pattern(&mut self, pattern: String);
256    fn get_search_matches(&self) -> Vec<(usize, usize)>;
257    fn set_search_matches(&mut self, matches: Vec<(usize, usize)>);
258    fn get_current_match(&self) -> Option<(usize, usize)>;
259    fn set_current_match(&mut self, match_pos: Option<(usize, usize)>);
260    fn get_search_match_index(&self) -> usize;
261    fn set_search_match_index(&mut self, index: usize);
262    fn clear_search_state(&mut self);
263
264    // --- Column Search ---
265
266    // --- Column Statistics ---
267    fn get_column_stats(&self) -> Option<&ColumnStatistics>;
268    fn set_column_stats(&mut self, stats: Option<ColumnStatistics>);
269
270    // --- Sorting ---
271    fn get_sort_column(&self) -> Option<usize>;
272    fn set_sort_column(&mut self, column: Option<usize>);
273    fn get_sort_order(&self) -> SortOrder;
274    fn set_sort_order(&mut self, order: SortOrder);
275
276    // --- Display Options ---
277    fn is_compact_mode(&self) -> bool;
278    fn set_compact_mode(&mut self, compact: bool);
279    fn is_show_row_numbers(&self) -> bool;
280    fn set_show_row_numbers(&mut self, show: bool);
281    fn is_viewport_lock(&self) -> bool;
282    fn set_viewport_lock(&mut self, locked: bool);
283    fn get_viewport_lock_row(&self) -> Option<usize>;
284    fn set_viewport_lock_row(&mut self, row: Option<usize>);
285    // REMOVED: pinned_columns methods - DataView handles pinned columns
286    // REMOVED: hidden_columns methods - DataView handles column visibility
287    fn get_column_widths(&self) -> &Vec<u16>;
288    fn set_column_widths(&mut self, widths: Vec<u16>);
289    fn is_case_insensitive(&self) -> bool;
290    fn set_case_insensitive(&mut self, case_insensitive: bool);
291
292    // --- Buffer Metadata ---
293    fn get_name(&self) -> String;
294    fn set_name(&mut self, name: String);
295    fn get_file_path(&self) -> Option<&PathBuf>;
296    fn set_file_path(&mut self, path: Option<String>);
297    fn is_modified(&self) -> bool;
298    fn set_modified(&mut self, modified: bool);
299    fn get_last_query_source(&self) -> Option<String>;
300    fn set_last_query_source(&mut self, source: Option<String>);
301
302    // --- CSV/Data Source ---
303    // REMOVED: CSV/Cache methods - legacy data access patterns
304
305    // --- Input State ---
306    fn get_input_value(&self) -> String;
307    fn set_input_value(&mut self, value: String);
308    fn get_input_cursor(&self) -> usize;
309    fn set_input_cursor(&mut self, pos: usize);
310
311    // --- Advanced Operations ---
312    fn apply_filter(&mut self) -> Result<()>;
313    fn apply_sort(&mut self) -> Result<()>;
314    fn search(&mut self) -> Result<()>;
315    fn clear_filters(&mut self);
316    fn get_row_count(&self) -> usize;
317    fn get_column_count(&self) -> usize;
318    fn get_column_names(&self) -> Vec<String>;
319
320    // --- Edit State ---
321    fn get_undo_stack(&self) -> &Vec<(String, usize)>;
322    fn push_undo(&mut self, state: (String, usize));
323    fn pop_undo(&mut self) -> Option<(String, usize)>;
324    fn get_redo_stack(&self) -> &Vec<(String, usize)>;
325    fn push_redo(&mut self, state: (String, usize));
326    fn pop_redo(&mut self) -> Option<(String, usize)>;
327    fn clear_redo(&mut self);
328    fn get_kill_ring(&self) -> String;
329    fn set_kill_ring(&mut self, text: String);
330    fn is_kill_ring_empty(&self) -> bool;
331
332    // High-level undo/redo operations
333    fn perform_undo(&mut self) -> bool;
334    fn perform_redo(&mut self) -> bool;
335    fn save_state_for_undo(&mut self);
336
337    // --- Viewport State ---
338    fn get_last_visible_rows(&self) -> usize;
339    fn set_last_visible_rows(&mut self, rows: usize);
340
341    // --- Debug ---
342    fn debug_dump(&self) -> String;
343
344    // --- Input Management ---
345    fn get_input_text(&self) -> String;
346    fn set_input_text(&mut self, text: String);
347    fn handle_input_key(&mut self, event: KeyEvent) -> bool;
348    fn switch_input_mode(&mut self, multiline: bool);
349    fn get_input_cursor_position(&self) -> usize;
350    fn set_input_cursor_position(&mut self, position: usize);
351    fn is_input_multiline(&self) -> bool;
352
353    // --- History Navigation ---
354    fn navigate_history_up(&mut self, history: &[String]) -> bool;
355    fn navigate_history_down(&mut self, history: &[String]) -> bool;
356    fn reset_history_navigation(&mut self);
357
358    // --- Results Management ---
359    fn clear_results(&mut self);
360}
361
362/// Represents a single buffer/tab with its own independent state
363pub struct Buffer {
364    /// Unique identifier for this buffer
365    pub id: usize,
366
367    /// File path if loaded from file
368    pub file_path: Option<PathBuf>,
369
370    /// Display name (filename or "untitled")
371    pub name: String,
372
373    /// Whether this buffer has unsaved changes
374    pub modified: bool,
375
376    // --- Data State ---
377    pub datatable: Option<Arc<DataTable>>,
378    /// Original unmodified `DataTable` (preserved for query operations)
379    pub original_source: Option<Arc<DataTable>>,
380    /// `DataView` for applying filters like hidden columns without modifying the `DataTable`
381    pub dataview: Option<DataView>,
382
383    // --- UI State ---
384    pub mode: AppMode,
385    pub edit_mode: EditMode,
386    pub input: Input, // Legacy - kept for compatibility during migration
387    pub input_manager: Box<dyn InputManager>, // New unified input management
388    pub table_state: TableState,
389    pub last_results_row: Option<usize>,
390    pub last_scroll_offset: (usize, usize),
391
392    // --- Query State ---
393    pub last_query: String,
394    pub status_message: String,
395
396    // --- Filter/Search State ---
397    pub sort_state: SortState,
398    pub filter_state: FilterState,
399    pub fuzzy_filter_state: FuzzyFilterState,
400    pub search_state: SearchState,
401
402    pub column_stats: Option<ColumnStatistics>,
403
404    // --- View State (Consolidated) ---
405    pub view_state: ViewState,
406
407    // --- Display Options (not navigation-related) ---
408    pub column_widths: Vec<u16>,
409    pub compact_mode: bool,
410    pub show_row_numbers: bool,
411    pub case_insensitive: bool,
412
413    // --- Misc State ---
414    pub undo_stack: Vec<(String, usize)>,
415    pub redo_stack: Vec<(String, usize)>,
416    pub kill_ring: String,
417    pub last_visible_rows: usize,
418    pub last_query_source: Option<String>,
419
420    // --- Syntax Highlighting ---
421    pub highlighted_text_cache: Option<Vec<(String, Color)>>, // Cache of highlighted tokens
422    pub last_highlighted_text: String, // Track what text was highlighted to detect changes
423
424    // --- Input State Stack (for search/filter modes) ---
425    pub saved_input_state: Option<(String, usize)>, // Save input when entering search/filter
426}
427
428// Implement BufferAPI for Buffer
429impl BufferAPI for Buffer {
430    // --- Identity ---
431    fn get_id(&self) -> usize {
432        self.id
433    }
434
435    // --- Query and Results ---
436    fn get_query(&self) -> String {
437        // Use InputManager if available, fallback to legacy input
438        self.input_manager.get_text()
439    }
440
441    fn set_query(&mut self, query: String) {
442        // Update both InputManager and legacy field for compatibility
443        self.input_manager.set_text(query.clone());
444        self.input = Input::new(query.clone()).with_cursor(query.len());
445    }
446
447    // V50: Removed get_results/set_results - use get_datatable/set_datatable instead
448
449    fn get_last_query(&self) -> String {
450        self.last_query.clone()
451    }
452
453    fn set_last_query(&mut self, query: String) {
454        self.last_query = query;
455    }
456
457    // --- V50: DataTable is primary storage ---
458    fn get_datatable(&self) -> Option<&DataTable> {
459        self.datatable.as_ref().map(std::convert::AsRef::as_ref)
460    }
461
462    fn get_datatable_mut(&mut self) -> Option<&mut DataTable> {
463        // Can't mutate through Arc - need to make a new copy if mutation is needed
464        // For now, return None as we shouldn't be mutating the DataTable directly
465        None
466    }
467
468    fn has_datatable(&self) -> bool {
469        self.datatable.is_some()
470    }
471
472    fn get_original_source(&self) -> Option<&DataTable> {
473        self.original_source
474            .as_ref()
475            .map(std::convert::AsRef::as_ref)
476    }
477
478    fn set_datatable(&mut self, datatable: Option<Arc<DataTable>>) {
479        debug!(
480            "V50: Setting DataTable with {} rows, {} columns",
481            datatable.as_ref().map_or(0, |d| d.row_count()),
482            datatable.as_ref().map_or(0, |d| d.column_count())
483        );
484
485        // Log current state
486        if let Some(ref current) = self.datatable {
487            debug!(
488                "V50: Current DataTable has {} columns: {:?}",
489                current.column_count(),
490                current.column_names()
491            );
492        }
493
494        if let Some(ref original) = self.original_source {
495            debug!(
496                "V50: Original source has {} columns: {:?}",
497                original.column_count(),
498                original.column_names()
499            );
500        }
501
502        // Preserve the original source if this is the first data load
503        // Only update original_source if we don't have one yet
504        if datatable.is_some() && self.original_source.is_none() {
505            self.original_source = datatable.clone();
506            debug!(
507                "V50: Preserving original source DataTable with {} columns",
508                datatable.as_ref().map_or(0, |d| d.column_count())
509            );
510        }
511
512        // When setting a DataTable, also create a DataView for it
513        // This ensures we always have a DataView as the source of truth for column visibility
514        if let Some(dt) = &datatable {
515            // Use the existing Arc, don't clone the DataTable!
516            let mut view = crate::data::data_view::DataView::new(dt.clone());
517
518            // Apply any existing hidden columns from the previous DataView
519            if let Some(old_view) = &self.dataview {
520                // Get list of hidden column names from old view
521                let old_all_cols = old_view.source().column_names();
522                let old_visible_cols = old_view.column_names();
523
524                for col_name in &old_all_cols {
525                    if !old_visible_cols.contains(col_name) {
526                        // This column was hidden in the old view
527                        view.hide_column_by_name(col_name);
528                    }
529                }
530            }
531
532            // Optimize memory after setting up the view
533            view.shrink_to_fit();
534
535            // DataView now handles column visibility directly
536            self.dataview = Some(view);
537        } else {
538            self.dataview = None;
539        }
540
541        // IMPORTANT: Never replace the datatable if we have an original source
542        // and the new table has fewer columns (indicating it's a query result)
543        if let Some(ref original) = self.original_source {
544            if let Some(ref new_dt) = datatable {
545                if new_dt.column_count() < original.column_count() {
546                    debug!(
547                        "V50: WARNING - Attempted to replace datatable with fewer columns ({} < {}). Keeping original.",
548                        new_dt.column_count(),
549                        original.column_count()
550                    );
551                    // Don't replace the datatable with a reduced one
552                    return;
553                }
554            }
555        }
556
557        self.datatable = datatable;
558    }
559
560    fn set_results_as_datatable(&mut self, response: Option<QueryResponse>) -> Result<(), String> {
561        if let Some(ref resp) = response {
562            debug!("V50: Converting QueryResponse to DataTable");
563            let table_name = resp.table.as_deref().unwrap_or("data");
564            match DataTable::from_query_response(resp, table_name) {
565                Ok(datatable) => {
566                    debug!(
567                        "V50: Stored DataTable with {} rows, {} columns",
568                        datatable.row_count(),
569                        datatable.column_count()
570                    );
571                    self.datatable = Some(Arc::new(datatable));
572                    Ok(())
573                }
574                Err(e) => {
575                    let err_msg = format!("V50: Failed to create DataTable: {e}");
576                    debug!("{}", err_msg);
577                    self.datatable = None;
578                    Err(err_msg)
579                }
580            }
581        } else {
582            self.datatable = None;
583            Ok(())
584        }
585    }
586
587    // --- V51: DataView support (direct query results) ---
588    fn get_dataview(&self) -> Option<&DataView> {
589        self.dataview.as_ref()
590    }
591    fn get_dataview_mut(&mut self) -> Option<&mut DataView> {
592        self.dataview.as_mut()
593    }
594    fn set_dataview(&mut self, dataview: Option<DataView>) {
595        debug!(
596            "V51: Setting DataView with {} rows",
597            dataview
598                .as_ref()
599                .map_or(0, super::data::data_view::DataView::row_count)
600        );
601        self.dataview = dataview;
602    }
603    fn has_dataview(&self) -> bool {
604        self.dataview.is_some()
605    }
606
607    // --- Mode and Status ---
608    fn get_mode(&self) -> AppMode {
609        self.mode.clone()
610    }
611
612    fn set_mode(&mut self, mode: AppMode) {
613        self.mode = mode;
614    }
615
616    fn get_edit_mode(&self) -> EditMode {
617        self.edit_mode.clone()
618    }
619
620    fn set_edit_mode(&mut self, mode: EditMode) {
621        self.edit_mode = mode;
622    }
623
624    fn get_status_message(&self) -> String {
625        self.status_message.clone()
626    }
627
628    fn set_status_message(&mut self, message: String) {
629        self.status_message = message;
630    }
631
632    // --- Table Navigation ---
633    fn get_selected_row(&self) -> Option<usize> {
634        // The crosshair is the position the table actually renders, so derive the
635        // selection from it rather than from table_state. table_state is only written
636        // by row navigation and query execution, so it stays None after a file load
637        // (or after a filter that leaves a single row, where j/k never fires) - which
638        // used to make yank report "No row selected" on a perfectly visible cell.
639        //
640        // None now means "there is no data", and the row is clamped to the current
641        // view so a filter that shrinks the results can't leave us reading past the end.
642        let row_count = self.visible_row_count();
643        if row_count == 0 {
644            None
645        } else {
646            Some(self.view_state.crosshair_row.min(row_count - 1))
647        }
648    }
649
650    fn set_selected_row(&mut self, row: Option<usize>) {
651        if let Some(r) = row {
652            self.view_state.crosshair_row = r;
653            // Also update table_state for compatibility during migration
654            self.table_state.select(Some(r));
655        } else {
656            // When setting to None, reset crosshair to 0 but clear table selection
657            self.view_state.crosshair_row = 0;
658            self.table_state.select(None);
659        }
660    }
661
662    fn get_current_column(&self) -> usize {
663        self.view_state.crosshair_col
664    }
665
666    fn set_current_column(&mut self, col: usize) {
667        self.view_state.crosshair_col = col;
668    }
669
670    fn get_scroll_offset(&self) -> (usize, usize) {
671        self.view_state.scroll_offset
672    }
673
674    fn set_scroll_offset(&mut self, offset: (usize, usize)) {
675        self.view_state.scroll_offset = offset;
676    }
677
678    fn get_last_results_row(&self) -> Option<usize> {
679        self.last_results_row
680    }
681
682    fn set_last_results_row(&mut self, row: Option<usize>) {
683        self.last_results_row = row;
684    }
685
686    fn get_last_scroll_offset(&self) -> (usize, usize) {
687        self.last_scroll_offset
688    }
689
690    fn set_last_scroll_offset(&mut self, offset: (usize, usize)) {
691        self.last_scroll_offset = offset;
692    }
693
694    // --- Filtering ---
695    fn get_filter_pattern(&self) -> String {
696        self.filter_state.pattern.clone()
697    }
698
699    fn set_filter_pattern(&mut self, pattern: String) {
700        self.filter_state.pattern = pattern;
701    }
702
703    fn is_filter_active(&self) -> bool {
704        self.filter_state.active
705    }
706
707    fn set_filter_active(&mut self, active: bool) {
708        self.filter_state.active = active;
709    }
710
711    // REMOVED: get_filtered_data/set_filtered_data implementations
712
713    // --- Fuzzy Filter ---
714    fn get_fuzzy_filter_pattern(&self) -> String {
715        self.fuzzy_filter_state.pattern.clone()
716    }
717
718    fn set_fuzzy_filter_pattern(&mut self, pattern: String) {
719        self.fuzzy_filter_state.pattern = pattern;
720    }
721
722    fn is_fuzzy_filter_active(&self) -> bool {
723        self.fuzzy_filter_state.active
724    }
725
726    fn set_fuzzy_filter_active(&mut self, active: bool) {
727        self.fuzzy_filter_state.active = active;
728    }
729
730    fn get_fuzzy_filter_indices(&self) -> &Vec<usize> {
731        &self.fuzzy_filter_state.filtered_indices
732    }
733
734    fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>) {
735        self.fuzzy_filter_state.filtered_indices = indices;
736    }
737
738    fn clear_fuzzy_filter(&mut self) {
739        self.fuzzy_filter_state.pattern.clear();
740        self.fuzzy_filter_state.active = false;
741        self.fuzzy_filter_state.filtered_indices.clear();
742    }
743
744    // --- Search ---
745    fn get_search_pattern(&self) -> String {
746        self.search_state.pattern.clone()
747    }
748
749    fn set_search_pattern(&mut self, pattern: String) {
750        self.search_state.pattern = pattern;
751    }
752
753    fn get_search_matches(&self) -> Vec<(usize, usize)> {
754        self.search_state.matches.clone()
755    }
756
757    fn set_search_matches(&mut self, matches: Vec<(usize, usize)>) {
758        self.search_state.matches = matches;
759    }
760
761    fn get_current_match(&self) -> Option<(usize, usize)> {
762        self.search_state.current_match
763    }
764
765    fn set_current_match(&mut self, match_pos: Option<(usize, usize)>) {
766        self.search_state.current_match = match_pos;
767    }
768
769    fn get_search_match_index(&self) -> usize {
770        self.search_state.match_index
771    }
772
773    fn set_search_match_index(&mut self, index: usize) {
774        self.search_state.match_index = index;
775    }
776
777    fn clear_search_state(&mut self) {
778        self.search_state.pattern.clear();
779        self.search_state.matches.clear();
780        self.search_state.current_match = None;
781        self.search_state.match_index = 0;
782    }
783
784    // --- Column Search ---
785
786    fn get_column_stats(&self) -> Option<&ColumnStatistics> {
787        self.column_stats.as_ref()
788    }
789
790    fn set_column_stats(&mut self, stats: Option<ColumnStatistics>) {
791        self.column_stats = stats;
792    }
793
794    // --- Sorting ---
795    fn get_sort_column(&self) -> Option<usize> {
796        self.sort_state.column
797    }
798
799    fn set_sort_column(&mut self, column: Option<usize>) {
800        self.sort_state.column = column;
801    }
802
803    fn get_sort_order(&self) -> SortOrder {
804        self.sort_state.order
805    }
806
807    fn set_sort_order(&mut self, order: SortOrder) {
808        self.sort_state.order = order;
809    }
810
811    // --- Display Options ---
812    fn is_compact_mode(&self) -> bool {
813        self.compact_mode
814    }
815
816    fn set_compact_mode(&mut self, compact: bool) {
817        self.compact_mode = compact;
818    }
819
820    fn is_show_row_numbers(&self) -> bool {
821        self.show_row_numbers
822    }
823
824    fn set_show_row_numbers(&mut self, show: bool) {
825        self.show_row_numbers = show;
826    }
827
828    fn is_viewport_lock(&self) -> bool {
829        self.view_state.viewport_lock
830    }
831
832    fn set_viewport_lock(&mut self, locked: bool) {
833        self.view_state.viewport_lock = locked;
834    }
835
836    fn get_viewport_lock_row(&self) -> Option<usize> {
837        // Return current crosshair row when viewport is locked
838        if self.view_state.viewport_lock {
839            Some(self.view_state.crosshair_row)
840        } else {
841            None
842        }
843    }
844
845    fn set_viewport_lock_row(&mut self, row: Option<usize>) {
846        // When setting viewport lock row, update the crosshair position
847        if let Some(r) = row {
848            self.view_state.crosshair_row = r;
849            self.view_state.viewport_lock = true;
850        }
851    }
852
853    fn get_column_widths(&self) -> &Vec<u16> {
854        &self.column_widths
855    }
856
857    fn set_column_widths(&mut self, widths: Vec<u16>) {
858        self.column_widths = widths;
859    }
860
861    fn is_case_insensitive(&self) -> bool {
862        self.case_insensitive
863    }
864
865    fn set_case_insensitive(&mut self, case_insensitive: bool) {
866        self.case_insensitive = case_insensitive;
867    }
868
869    // --- Buffer Metadata ---
870    fn get_name(&self) -> String {
871        self.name.clone()
872    }
873
874    fn set_name(&mut self, name: String) {
875        self.name = name;
876    }
877
878    fn get_file_path(&self) -> Option<&PathBuf> {
879        self.file_path.as_ref()
880    }
881
882    fn set_file_path(&mut self, path: Option<String>) {
883        self.file_path = path.map(PathBuf::from);
884    }
885
886    fn is_modified(&self) -> bool {
887        self.modified
888    }
889
890    fn set_modified(&mut self, modified: bool) {
891        self.modified = modified;
892    }
893
894    fn get_last_query_source(&self) -> Option<String> {
895        self.last_query_source.clone()
896    }
897
898    fn set_last_query_source(&mut self, source: Option<String>) {
899        self.last_query_source = source;
900    }
901
902    // --- Input State ---
903    fn get_input_value(&self) -> String {
904        self.input.value().to_string()
905    }
906
907    fn set_input_value(&mut self, value: String) {
908        let cursor = value.len();
909        self.input = Input::new(value).with_cursor(cursor);
910    }
911
912    fn get_input_cursor(&self) -> usize {
913        self.input.cursor()
914    }
915
916    fn set_input_cursor(&mut self, pos: usize) {
917        let value = self.input.value().to_string();
918        self.input = Input::new(value).with_cursor(pos);
919    }
920
921    // --- Advanced Operations ---
922    fn apply_filter(&mut self) -> Result<()> {
923        // TODO: Implement actual filtering logic
924        Ok(())
925    }
926
927    fn apply_sort(&mut self) -> Result<()> {
928        // TODO: Implement actual sorting logic
929        Ok(())
930    }
931
932    fn search(&mut self) -> Result<()> {
933        // TODO: Implement actual search logic
934        Ok(())
935    }
936
937    fn clear_filters(&mut self) {
938        self.filter_state.active = false;
939        self.filter_state.pattern.clear();
940        self.fuzzy_filter_state.active = false;
941        self.fuzzy_filter_state.pattern.clear();
942        // DataView handles the actual filtering
943    }
944
945    fn get_row_count(&self) -> usize {
946        if let Some(dataview) = &self.dataview {
947            dataview.row_count()
948        } else if let Some(datatable) = &self.datatable {
949            datatable.row_count()
950        } else {
951            0
952        }
953    }
954
955    fn get_column_count(&self) -> usize {
956        if let Some(datatable) = &self.datatable {
957            return datatable.column_count();
958        }
959        0
960    }
961
962    fn get_column_names(&self) -> Vec<String> {
963        if let Some(datatable) = &self.datatable {
964            return datatable.column_names();
965        }
966        Vec::new()
967    }
968
969    // --- Edit State ---
970    fn get_undo_stack(&self) -> &Vec<(String, usize)> {
971        &self.undo_stack
972    }
973
974    fn push_undo(&mut self, state: (String, usize)) {
975        self.undo_stack.push(state);
976        if self.undo_stack.len() > 100 {
977            self.undo_stack.remove(0);
978        }
979    }
980
981    fn pop_undo(&mut self) -> Option<(String, usize)> {
982        self.undo_stack.pop()
983    }
984
985    fn get_redo_stack(&self) -> &Vec<(String, usize)> {
986        &self.redo_stack
987    }
988
989    fn push_redo(&mut self, state: (String, usize)) {
990        self.redo_stack.push(state);
991    }
992
993    fn pop_redo(&mut self) -> Option<(String, usize)> {
994        self.redo_stack.pop()
995    }
996
997    fn clear_redo(&mut self) {
998        self.redo_stack.clear();
999    }
1000
1001    fn perform_undo(&mut self) -> bool {
1002        if let Some((prev_text, prev_cursor)) = self.pop_undo() {
1003            // Save current state to redo stack
1004            let current_state = (self.get_input_text(), self.get_input_cursor_position());
1005            self.push_redo(current_state);
1006
1007            // Restore previous state
1008            self.set_input_text(prev_text);
1009            self.set_input_cursor_position(prev_cursor);
1010            true
1011        } else {
1012            false
1013        }
1014    }
1015
1016    fn perform_redo(&mut self) -> bool {
1017        if let Some((next_text, next_cursor)) = self.pop_redo() {
1018            // Save current state to undo stack
1019            let current_state = (self.get_input_text(), self.get_input_cursor_position());
1020            self.push_undo(current_state);
1021
1022            // Restore next state
1023            self.set_input_text(next_text);
1024            self.set_input_cursor_position(next_cursor);
1025            true
1026        } else {
1027            false
1028        }
1029    }
1030
1031    fn save_state_for_undo(&mut self) {
1032        let current_state = (self.get_input_text(), self.get_input_cursor_position());
1033        self.push_undo(current_state);
1034        self.clear_redo();
1035    }
1036
1037    fn get_kill_ring(&self) -> String {
1038        self.kill_ring.clone()
1039    }
1040
1041    fn set_kill_ring(&mut self, text: String) {
1042        self.kill_ring = text;
1043    }
1044
1045    fn is_kill_ring_empty(&self) -> bool {
1046        self.kill_ring.is_empty()
1047    }
1048
1049    // --- Viewport State ---
1050    fn get_last_visible_rows(&self) -> usize {
1051        self.last_visible_rows
1052    }
1053
1054    fn set_last_visible_rows(&mut self, rows: usize) {
1055        self.last_visible_rows = rows;
1056    }
1057
1058    fn debug_dump(&self) -> String {
1059        let mut output = String::new();
1060        output.push_str("=== BUFFER DEBUG DUMP ===\n");
1061        output.push_str(&format!("Buffer ID: {}\n", self.id));
1062        output.push_str(&format!("Name: {}\n", self.name));
1063        output.push_str(&format!("File Path: {:?}\n", self.file_path));
1064        output.push_str(&format!("Modified: {}\n", self.modified));
1065        output.push_str("\n--- Modes ---\n");
1066        output.push_str(&format!("App Mode: {:?}\n", self.mode));
1067        output.push_str(&format!("Edit Mode: {:?}\n", self.edit_mode));
1068        output.push_str("\n--- Query State ---\n");
1069        output.push_str(&format!("Current Input: '{}'\n", self.input.value()));
1070        output.push_str(&format!("Input Cursor: {}\n", self.input.cursor()));
1071        output.push_str(&format!("Last Query: '{}'\n", self.last_query));
1072        output.push_str(&format!("Status Message: '{}'\n", self.status_message));
1073        output.push_str(&format!(
1074            "Last Query Source: {:?}\n",
1075            self.last_query_source
1076        ));
1077        output.push_str("\n--- Results ---\n");
1078        output.push_str(&format!("Has DataTable: {}\n", self.datatable.is_some()));
1079        output.push_str(&format!("Row Count: {}\n", self.get_row_count()));
1080        output.push_str(&format!("Column Count: {}\n", self.get_column_count()));
1081        output.push_str(&format!(
1082            "Selected Row: {:?}\n",
1083            self.table_state.selected()
1084        ));
1085        output.push_str(&format!(
1086            "Current Column: {}\n",
1087            self.view_state.crosshair_col
1088        ));
1089        output.push_str(&format!(
1090            "Scroll Offset: {:?}\n",
1091            self.view_state.scroll_offset
1092        ));
1093        output.push_str("\n--- Filtering ---\n");
1094        output.push_str(&format!("Filter Active: {}\n", self.filter_state.active));
1095        output.push_str(&format!(
1096            "Filter Pattern: '{}'\n",
1097            self.filter_state.pattern
1098        ));
1099        output.push_str("Filtering: Handled by DataView\n");
1100        output.push_str(&format!(
1101            "Fuzzy Filter Active: {}\n",
1102            self.fuzzy_filter_state.active
1103        ));
1104        output.push_str(&format!(
1105            "Fuzzy Pattern: '{}'\n",
1106            self.fuzzy_filter_state.pattern
1107        ));
1108        output.push_str("\n--- Search ---\n");
1109        output.push_str(&format!(
1110            "Search Pattern: '{}'\n",
1111            self.search_state.pattern
1112        ));
1113        output.push_str(&format!(
1114            "Search Matches: {} found\n",
1115            self.search_state.matches.len()
1116        ));
1117        output.push_str(&format!(
1118            "Current Match: {:?}\n",
1119            self.search_state.current_match
1120        ));
1121        output.push_str(&format!("Match Index: {}\n", self.search_state.match_index));
1122        output.push_str("\n--- Column Search ---\n");
1123        output.push_str(&format!(
1124            "Column Search Pattern: '{}'\n",
1125            "<migrated>" // Column search migrated to AppStateContainer
1126        ));
1127        output.push_str(&format!(
1128            "Matching Columns: {:?}\n",
1129            Vec::<(usize, String)>::new() // Column search migrated to AppStateContainer
1130        ));
1131        output.push_str("\n--- Sorting ---\n");
1132        output.push_str(&format!("Sort Column: {:?}\n", self.sort_state.column));
1133        output.push_str(&format!("Sort Order: {:?}\n", self.sort_state.order));
1134        output.push_str("\n--- Display Options ---\n");
1135        output.push_str(&format!("Compact Mode: {}\n", self.compact_mode));
1136        output.push_str(&format!("Show Row Numbers: {}\n", self.show_row_numbers));
1137        output.push_str(&format!("Case Insensitive: {}\n", self.case_insensitive));
1138        // Pinned columns now handled by DataView
1139        if let Some(ref dataview) = self.dataview {
1140            output.push_str(&format!(
1141                "Pinned Columns: {:?}\n",
1142                dataview.get_pinned_column_names()
1143            ));
1144        } else {
1145            output.push_str("Pinned Columns: []\n");
1146        }
1147        output.push_str(&format!("Column Widths: {:?}\n", self.column_widths));
1148        output.push_str(&format!("ViewState: {:?}\n", self.view_state));
1149        output.push_str("\n--- Data Source ---\n");
1150        output.push_str("Legacy CSV/Cache fields removed - using DataTable/DataView\n");
1151        output.push_str("\n--- Undo/Redo ---\n");
1152        output.push_str(&format!("Undo Stack Size: {}\n", self.undo_stack.len()));
1153        output.push_str(&format!("Redo Stack Size: {}\n", self.redo_stack.len()));
1154        output.push_str(&format!(
1155            "Kill Ring: '{}'\n",
1156            if self.kill_ring.len() > 50 {
1157                format!(
1158                    "{}... ({} chars)",
1159                    &self.kill_ring[..50],
1160                    self.kill_ring.len()
1161                )
1162            } else {
1163                self.kill_ring.clone()
1164            }
1165        ));
1166        output.push_str("\n--- Stats ---\n");
1167        output.push_str(&format!(
1168            "Has Column Stats: {}\n",
1169            self.column_stats.is_some()
1170        ));
1171        output.push_str(&format!("Last Visible Rows: {}\n", self.last_visible_rows));
1172        output.push_str(&format!("Last Results Row: {:?}\n", self.last_results_row));
1173        output.push_str(&format!(
1174            "Last Scroll Offset: {:?}\n",
1175            self.last_scroll_offset
1176        ));
1177        output.push_str("\n=== END BUFFER DEBUG ===\n");
1178        output
1179    }
1180
1181    // --- Input Management ---
1182    fn get_input_text(&self) -> String {
1183        self.input_manager.get_text()
1184    }
1185
1186    fn set_input_text(&mut self, text: String) {
1187        self.input_manager.set_text(text.clone());
1188        // Sync with legacy fields for compatibility
1189        self.input = Input::new(text.clone()).with_cursor(text.len());
1190    }
1191
1192    fn handle_input_key(&mut self, event: KeyEvent) -> bool {
1193        let result = self.input_manager.handle_key_event(event);
1194        // Sync with legacy fields after key handling
1195        self.sync_from_input_manager();
1196        result
1197    }
1198
1199    fn switch_input_mode(&mut self, _multiline: bool) {
1200        let current_text = self.input_manager.get_text();
1201        let cursor_pos = self.input_manager.get_cursor_position();
1202
1203        // Always use single-line mode
1204        self.edit_mode = EditMode::SingleLine;
1205        self.input_manager = create_single_line(current_text.clone());
1206        // Update legacy input
1207        self.input =
1208            Input::new(current_text.clone()).with_cursor(cursor_pos.min(current_text.len()));
1209
1210        // Try to restore cursor position
1211        self.input_manager.set_cursor_position(cursor_pos);
1212    }
1213
1214    fn get_input_cursor_position(&self) -> usize {
1215        self.input_manager.get_cursor_position()
1216    }
1217
1218    fn set_input_cursor_position(&mut self, position: usize) {
1219        self.input_manager.set_cursor_position(position);
1220        // Sync with legacy fields
1221        if self.edit_mode == EditMode::SingleLine {
1222            let text = self.input.value().to_string();
1223            self.input = Input::new(text).with_cursor(position);
1224        }
1225    }
1226
1227    fn is_input_multiline(&self) -> bool {
1228        self.input_manager.is_multiline()
1229    }
1230
1231    // --- History Navigation ---
1232    fn navigate_history_up(&mut self, history: &[String]) -> bool {
1233        // Set history if not already set
1234        self.input_manager.set_history(history.to_vec());
1235        let navigated = self.input_manager.history_previous();
1236        if navigated {
1237            // Sync to legacy fields
1238            self.sync_from_input_manager();
1239        }
1240        navigated
1241    }
1242
1243    fn navigate_history_down(&mut self, history: &[String]) -> bool {
1244        // Set history if not already set
1245        self.input_manager.set_history(history.to_vec());
1246        let navigated = self.input_manager.history_next();
1247        if navigated {
1248            // Sync to legacy fields
1249            self.sync_from_input_manager();
1250        }
1251        navigated
1252    }
1253
1254    fn reset_history_navigation(&mut self) {
1255        self.input_manager.reset_history_position();
1256    }
1257
1258    // --- Results Management ---
1259    fn clear_results(&mut self) {
1260        self.datatable = None;
1261        // DataView handles filtering
1262        self.table_state.select(None);
1263        self.last_results_row = None;
1264        self.view_state.scroll_offset = (0, 0);
1265        self.last_scroll_offset = (0, 0);
1266        self.column_widths.clear();
1267        self.status_message = "Results cleared".to_string();
1268        // Reset search/filter states
1269        self.filter_state.active = false;
1270        self.filter_state.pattern.clear();
1271        self.search_state.pattern.clear();
1272        self.search_state.matches.clear();
1273        self.search_state.current_match = None;
1274    }
1275}
1276
1277impl Buffer {
1278    /// Number of rows currently visible, preferring the `DataView` (which knows about
1279    /// filtering) and falling back to the raw `DataTable` for legacy buffers.
1280    fn visible_row_count(&self) -> usize {
1281        if let Some(ref dataview) = self.dataview {
1282            dataview.row_count()
1283        } else if let Some(ref datatable) = self.datatable {
1284            datatable.row_count()
1285        } else {
1286            0
1287        }
1288    }
1289
1290    /// Create a new empty buffer
1291    #[must_use]
1292    pub fn new(id: usize) -> Self {
1293        Self {
1294            id,
1295            file_path: None,
1296            name: format!("[Buffer {id}]"),
1297            modified: false,
1298
1299            // Legacy CSV/Cache fields removed
1300            datatable: None,
1301            original_source: None,
1302            dataview: None,
1303
1304            mode: AppMode::Command,
1305            edit_mode: EditMode::SingleLine,
1306            input: Input::default(),
1307            input_manager: create_single_line(String::new()),
1308            table_state: TableState::default(),
1309            last_results_row: None,
1310            last_scroll_offset: (0, 0),
1311
1312            last_query: String::new(),
1313            status_message: String::new(),
1314
1315            sort_state: SortState {
1316                column: None,
1317                order: SortOrder::None,
1318            },
1319            filter_state: FilterState::default(),
1320            fuzzy_filter_state: FuzzyFilterState::default(),
1321            search_state: SearchState::default(),
1322            // column_search_state: MIGRATED to AppStateContainer
1323            column_stats: None,
1324
1325            view_state: ViewState::default(),
1326            column_widths: Vec::new(),
1327            compact_mode: false,
1328            show_row_numbers: false,
1329            case_insensitive: false,
1330
1331            undo_stack: Vec::new(),
1332            redo_stack: Vec::new(),
1333            kill_ring: String::new(),
1334            last_visible_rows: 30,
1335            last_query_source: None,
1336
1337            highlighted_text_cache: None,
1338            last_highlighted_text: String::new(),
1339            saved_input_state: None,
1340        }
1341    }
1342
1343    /// Create a buffer from a CSV file
1344    #[must_use]
1345    pub fn from_csv(
1346        id: usize,
1347        path: PathBuf,
1348        _csv_client: CsvApiClient, // Kept for API compatibility but unused
1349        _table_name: String,       // Kept for API compatibility but unused
1350    ) -> Self {
1351        let name = path
1352            .file_name()
1353            .and_then(|n| n.to_str())
1354            .unwrap_or("unknown.csv")
1355            .to_string();
1356
1357        let mut buffer = Self::new(id);
1358        buffer.file_path = Some(path);
1359        buffer.name = name;
1360        // Legacy CSV fields removed - DataTable/DataView handles data
1361
1362        buffer
1363    }
1364
1365    /// Create a buffer from a JSON file
1366    #[must_use]
1367    pub fn from_json(
1368        id: usize,
1369        path: PathBuf,
1370        _csv_client: CsvApiClient, // Kept for API compatibility but unused
1371        _table_name: String,       // Kept for API compatibility but unused
1372    ) -> Self {
1373        let name = path
1374            .file_name()
1375            .and_then(|n| n.to_str())
1376            .unwrap_or("unknown.json")
1377            .to_string();
1378
1379        let mut buffer = Self::new(id);
1380        buffer.file_path = Some(path);
1381        buffer.name = name;
1382        // Legacy CSV fields removed - DataTable/DataView handles data
1383
1384        buffer
1385    }
1386
1387    /// Get display name for tab bar
1388    pub fn display_name(&self) -> String {
1389        if self.modified {
1390            format!("{}*", self.name)
1391        } else {
1392            self.name.clone()
1393        }
1394    }
1395
1396    /// Get short name for tab bar (truncated if needed)
1397    pub fn short_name(&self, max_len: usize) -> String {
1398        let display = self.display_name();
1399        if display.len() <= max_len {
1400            display
1401        } else {
1402            format!("{}...", &display[..max_len.saturating_sub(3)])
1403        }
1404    }
1405
1406    /// Check if buffer has a specific file open
1407    pub fn has_file(&self, path: &PathBuf) -> bool {
1408        self.file_path.as_ref() == Some(path)
1409    }
1410
1411    /// Sync from `InputManager` to legacy fields (for compatibility during migration)
1412    fn sync_from_input_manager(&mut self) {
1413        let text = self.input_manager.get_text();
1414        let cursor_pos = self.input_manager.get_cursor_position();
1415
1416        // Always sync to single-line input
1417        let text_len = text.len();
1418        self.input = Input::new(text).with_cursor(cursor_pos.min(text_len));
1419    }
1420
1421    /// Sync from legacy fields to `InputManager` (for compatibility during migration)
1422    fn sync_to_input_manager(&mut self) {
1423        // Always sync from single-line input
1424        let _text = self.input.value().to_string();
1425        self.input_manager = create_from_input(self.input.clone());
1426    }
1427
1428    // --- Cursor Movement Operations ---
1429    // These use the CursorOperations helper to provide intelligent
1430    // SQL-aware cursor movement and text manipulation
1431
1432    /// Move cursor to previous word boundary
1433    pub fn move_cursor_word_backward(&mut self) {
1434        let text = self.input_manager.get_text();
1435        let cursor_pos = self.input_manager.get_cursor_position();
1436        let new_pos = CursorOperations::find_word_boundary_backward(&text, cursor_pos);
1437        self.input_manager.set_cursor_position(new_pos);
1438        self.sync_from_input_manager();
1439        self.status_message = format!("Moved to position {new_pos} (word boundary)");
1440    }
1441
1442    /// Move cursor to next word boundary
1443    pub fn move_cursor_word_forward(&mut self) {
1444        let text = self.input_manager.get_text();
1445        let cursor_pos = self.input_manager.get_cursor_position();
1446        let new_pos = CursorOperations::find_word_boundary_forward(&text, cursor_pos);
1447        self.input_manager.set_cursor_position(new_pos);
1448        self.sync_from_input_manager();
1449    }
1450
1451    /// Delete word backward from cursor
1452    pub fn delete_word_backward(&mut self) {
1453        let text = self.input_manager.get_text();
1454        let cursor_pos = self.input_manager.get_cursor_position();
1455        let (new_text, new_cursor) = CursorOperations::delete_word_backward(&text, cursor_pos);
1456
1457        // Store deleted text in kill ring
1458        if cursor_pos > new_cursor {
1459            self.kill_ring = text[new_cursor..cursor_pos].to_string();
1460        }
1461
1462        self.input_manager.set_text(new_text);
1463        self.input_manager.set_cursor_position(new_cursor);
1464        self.sync_from_input_manager();
1465    }
1466
1467    /// Delete word forward from cursor
1468    pub fn delete_word_forward(&mut self) {
1469        let text = self.input_manager.get_text();
1470        let cursor_pos = self.input_manager.get_cursor_position();
1471        let (new_text, new_cursor) = CursorOperations::delete_word_forward(&text, cursor_pos);
1472
1473        // Store deleted text in kill ring
1474        let word_end = CursorOperations::find_word_boundary_forward(&text, cursor_pos);
1475        if word_end > cursor_pos {
1476            self.kill_ring = text[cursor_pos..word_end].to_string();
1477        }
1478
1479        self.input_manager.set_text(new_text);
1480        self.input_manager.set_cursor_position(new_cursor);
1481        self.sync_from_input_manager();
1482    }
1483
1484    /// Kill line from cursor to end
1485    pub fn kill_line(&mut self) {
1486        let text = self.input_manager.get_text();
1487        let cursor_pos = self.input_manager.get_cursor_position();
1488        let (new_text, killed) = CursorOperations::kill_line(&text, cursor_pos);
1489
1490        self.kill_ring = killed;
1491        self.input_manager.set_text(new_text);
1492        self.sync_from_input_manager();
1493    }
1494
1495    /// Kill line from start to cursor
1496    pub fn kill_line_backward(&mut self) {
1497        let text = self.input_manager.get_text();
1498        let cursor_pos = self.input_manager.get_cursor_position();
1499        let (new_text, killed, new_cursor) =
1500            CursorOperations::kill_line_backward(&text, cursor_pos);
1501
1502        self.kill_ring = killed;
1503        self.input_manager.set_text(new_text);
1504        self.input_manager.set_cursor_position(new_cursor);
1505        self.sync_from_input_manager();
1506    }
1507
1508    /// Jump to previous SQL token
1509    pub fn jump_to_prev_token(&mut self) {
1510        let text = self.input_manager.get_text();
1511        let cursor_pos = self.input_manager.get_cursor_position();
1512        let new_pos = CursorOperations::jump_to_prev_token(&text, cursor_pos);
1513        self.input_manager.set_cursor_position(new_pos);
1514        self.sync_from_input_manager();
1515    }
1516
1517    /// Jump to next SQL token
1518    pub fn jump_to_next_token(&mut self) {
1519        let text = self.input_manager.get_text();
1520        let cursor_pos = self.input_manager.get_cursor_position();
1521        let new_pos = CursorOperations::jump_to_next_token(&text, cursor_pos);
1522        self.input_manager.set_cursor_position(new_pos);
1523        self.sync_from_input_manager();
1524    }
1525
1526    /// Yank (paste) from kill ring
1527    pub fn yank(&mut self) {
1528        if !self.kill_ring.is_empty() {
1529            self.save_state_for_undo();
1530
1531            let text = self.input_manager.get_text();
1532            let cursor_pos = self.input_manager.get_cursor_position();
1533
1534            // Insert kill ring content at cursor position
1535            let before = text.chars().take(cursor_pos).collect::<String>();
1536            let after = text.chars().skip(cursor_pos).collect::<String>();
1537            let new_text = format!("{}{}{}", before, &self.kill_ring, after);
1538            let new_cursor = cursor_pos + self.kill_ring.len();
1539
1540            self.input_manager.set_text(new_text);
1541            self.input_manager.set_cursor_position(new_cursor);
1542            self.sync_from_input_manager();
1543        }
1544    }
1545
1546    /// Expand SELECT * to column names using schema information
1547    pub fn expand_asterisk(&mut self, parser: &HybridParser) -> bool {
1548        let query = self.input_manager.get_text();
1549        let query_upper = query.to_uppercase();
1550
1551        // Find SELECT * pattern
1552        if let Some(select_pos) = query_upper.find("SELECT") {
1553            if let Some(star_pos) = query_upper[select_pos..].find('*') {
1554                let star_abs_pos = select_pos + star_pos;
1555
1556                // Find FROM clause after the *
1557                if let Some(from_rel_pos) = query_upper[star_abs_pos..].find("FROM") {
1558                    let from_abs_pos = star_abs_pos + from_rel_pos;
1559
1560                    // Extract table name after FROM
1561                    let after_from = &query[from_abs_pos + 4..].trim_start();
1562                    let table_name = after_from
1563                        .split_whitespace()
1564                        .next()
1565                        .unwrap_or("")
1566                        .trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_');
1567
1568                    if !table_name.is_empty() {
1569                        // Get columns from the schema
1570                        let columns = parser.get_table_columns(table_name);
1571
1572                        if columns.is_empty() {
1573                            self.status_message =
1574                                format!("No columns found for table '{table_name}'");
1575                        } else {
1576                            // Build the replacement with all columns
1577                            let columns_str = columns.join(", ");
1578
1579                            // Replace * with the column list
1580                            let before_star = &query[..star_abs_pos];
1581                            let after_star = &query[star_abs_pos + 1..];
1582                            let new_query = format!("{before_star}{columns_str}{after_star}");
1583
1584                            // Update the input
1585                            self.input_manager.set_text(new_query.clone());
1586                            self.input_manager.set_cursor_position(new_query.len());
1587                            self.sync_from_input_manager();
1588
1589                            self.status_message =
1590                                format!("Expanded * to {} columns", columns.len());
1591                            return true;
1592                        }
1593                    }
1594                }
1595            }
1596        }
1597
1598        self.status_message = "No SELECT * pattern found to expand".to_string();
1599        false
1600    }
1601
1602    /// Expand SELECT * to only visible column names
1603    pub fn expand_asterisk_visible(&mut self) -> bool {
1604        let query = self.input_manager.get_text();
1605        let query_upper = query.to_uppercase();
1606
1607        // Find SELECT * pattern
1608        if let Some(select_pos) = query_upper.find("SELECT") {
1609            if let Some(star_pos) = query_upper[select_pos..].find('*') {
1610                let star_abs_pos = select_pos + star_pos;
1611
1612                // Get visible columns from the DataView
1613                if let Some(dataview) = &self.dataview {
1614                    let visible_columns = dataview.get_display_column_names();
1615
1616                    if !visible_columns.is_empty() {
1617                        // Build the replacement with visible columns only
1618                        let columns_str = visible_columns.join(", ");
1619
1620                        // Replace * with the column list
1621                        let before_star = &query[..star_abs_pos];
1622                        let after_star = &query[star_abs_pos + 1..];
1623                        let new_query = format!("{before_star}{columns_str}{after_star}");
1624
1625                        // Update the input
1626                        self.input_manager.set_text(new_query.clone());
1627                        self.input_manager.set_cursor_position(new_query.len());
1628                        self.sync_from_input_manager();
1629
1630                        self.status_message =
1631                            format!("Expanded * to {} visible columns", visible_columns.len());
1632                        return true;
1633                    }
1634                    self.status_message = "No visible columns available".to_string();
1635                } else {
1636                    self.status_message = "No data loaded to expand from".to_string();
1637                }
1638            }
1639        }
1640
1641        self.status_message = "No SELECT * pattern found to expand".to_string();
1642        false
1643    }
1644}
1645
1646// Manual Clone implementation for Buffer due to Box<dyn InputManager>
1647impl Clone for Buffer {
1648    fn clone(&self) -> Self {
1649        // Always clone as single-line mode
1650        let input_manager = create_from_input(self.input.clone());
1651
1652        Self {
1653            id: self.id,
1654            file_path: self.file_path.clone(),
1655            name: self.name.clone(),
1656            modified: self.modified,
1657            // Legacy CSV/Cache fields removed
1658            datatable: self.datatable.clone(),
1659            original_source: self.original_source.clone(),
1660            dataview: self.dataview.clone(),
1661            mode: self.mode.clone(),
1662            edit_mode: self.edit_mode.clone(),
1663            input: self.input.clone(),
1664            input_manager,
1665            table_state: self.table_state.clone(),
1666            last_results_row: self.last_results_row,
1667            last_scroll_offset: self.last_scroll_offset,
1668            last_query: self.last_query.clone(),
1669            status_message: self.status_message.clone(),
1670            sort_state: self.sort_state.clone(),
1671            filter_state: self.filter_state.clone(),
1672            fuzzy_filter_state: self.fuzzy_filter_state.clone(),
1673            search_state: self.search_state.clone(),
1674            // column_search_state: MIGRATED to AppStateContainer
1675            column_stats: self.column_stats.clone(),
1676            view_state: self.view_state.clone(),
1677            column_widths: self.column_widths.clone(),
1678            compact_mode: self.compact_mode,
1679            show_row_numbers: self.show_row_numbers,
1680            case_insensitive: self.case_insensitive,
1681            undo_stack: self.undo_stack.clone(),
1682            redo_stack: self.redo_stack.clone(),
1683            kill_ring: self.kill_ring.clone(),
1684            last_visible_rows: self.last_visible_rows,
1685            last_query_source: self.last_query_source.clone(),
1686            highlighted_text_cache: self.highlighted_text_cache.clone(),
1687            last_highlighted_text: self.last_highlighted_text.clone(),
1688            saved_input_state: self.saved_input_state.clone(),
1689        }
1690    }
1691}
1692
1693/// Manages multiple buffers and switching between them
1694pub struct BufferManager {
1695    buffers: Vec<Buffer>,
1696    current_buffer_index: usize,
1697    next_buffer_id: usize,
1698}
1699
1700impl Default for BufferManager {
1701    fn default() -> Self {
1702        Self::new()
1703    }
1704}
1705
1706impl BufferManager {
1707    #[must_use]
1708    pub fn new() -> Self {
1709        Self {
1710            buffers: Vec::new(),
1711            current_buffer_index: 0,
1712            next_buffer_id: 1,
1713        }
1714    }
1715
1716    /// Add a new buffer and make it current
1717    pub fn add_buffer(&mut self, mut buffer: Buffer) -> usize {
1718        buffer.id = self.next_buffer_id;
1719        self.next_buffer_id += 1;
1720
1721        let index = self.buffers.len();
1722        self.buffers.push(buffer);
1723        self.current_buffer_index = index;
1724        index
1725    }
1726
1727    /// Get current buffer
1728    #[must_use]
1729    pub fn current(&self) -> Option<&Buffer> {
1730        self.buffers.get(self.current_buffer_index)
1731    }
1732
1733    /// Get current buffer mutably
1734    pub fn current_mut(&mut self) -> Option<&mut Buffer> {
1735        self.buffers.get_mut(self.current_buffer_index)
1736    }
1737
1738    /// Switch to next buffer
1739    pub fn next_buffer(&mut self) {
1740        if !self.buffers.is_empty() {
1741            self.current_buffer_index = (self.current_buffer_index + 1) % self.buffers.len();
1742        }
1743    }
1744
1745    /// Switch to previous buffer
1746    pub fn prev_buffer(&mut self) {
1747        if !self.buffers.is_empty() {
1748            if self.current_buffer_index == 0 {
1749                self.current_buffer_index = self.buffers.len() - 1;
1750            } else {
1751                self.current_buffer_index -= 1;
1752            }
1753        }
1754    }
1755
1756    /// Switch to buffer by index
1757    pub fn switch_to(&mut self, index: usize) {
1758        if index < self.buffers.len() {
1759            self.current_buffer_index = index;
1760        }
1761    }
1762
1763    /// Close current buffer
1764    pub fn close_current(&mut self) -> bool {
1765        if self.buffers.len() <= 1 {
1766            return false; // Don't close last buffer
1767        }
1768
1769        self.buffers.remove(self.current_buffer_index);
1770
1771        // Adjust current index if needed
1772        if self.current_buffer_index >= self.buffers.len() {
1773            self.current_buffer_index = self.buffers.len() - 1;
1774        }
1775
1776        true
1777    }
1778
1779    /// Find buffer by file path
1780    #[must_use]
1781    pub fn find_by_path(&self, path: &PathBuf) -> Option<usize> {
1782        self.buffers.iter().position(|b| b.has_file(path))
1783    }
1784
1785    /// Get all buffers for display
1786    #[must_use]
1787    pub fn all_buffers(&self) -> &[Buffer] {
1788        &self.buffers
1789    }
1790
1791    /// Get current buffer index
1792    #[must_use]
1793    pub fn current_index(&self) -> usize {
1794        self.current_buffer_index
1795    }
1796
1797    /// Check if we have multiple buffers
1798    #[must_use]
1799    pub fn has_multiple(&self) -> bool {
1800        self.buffers.len() > 1
1801    }
1802
1803    /// Clear all buffers (used when loading a new file)
1804    pub fn clear_all(&mut self) {
1805        self.buffers.clear();
1806        self.current_buffer_index = 0;
1807    }
1808}