1use crate::api_client::QueryResponse; use crate::csv_datasource::CsvApiClient; use 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#[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(), 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#[derive(Clone, Debug, PartialEq)]
104pub enum SelectionMode {
105 Row,
106 Cell,
107 Column,
108}
109
110#[derive(Clone, Debug)]
113pub struct ViewState {
114 pub crosshair_row: usize,
116 pub crosshair_col: usize,
117 pub scroll_offset: (usize, usize),
118
119 pub selection_mode: SelectionMode,
121 pub selected_cells: Vec<(usize, usize)>,
122 pub selection_anchor: Option<(usize, usize)>,
123
124 pub viewport_lock: bool,
126 pub cursor_lock: bool,
127
128 pub navigation_history: Vec<(usize, usize)>,
130 pub history_index: usize,
131
132 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 pub total_count: usize,
173 pub null_count: usize,
174 pub unique_count: usize,
175 pub frequency_map: Option<BTreeMap<String, usize>>,
177 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
185pub trait BufferAPI: Send + Sync {
193 fn get_id(&self) -> usize;
195 fn get_query(&self) -> String;
197 fn set_query(&mut self, query: String);
198 fn get_last_query(&self) -> String;
200 fn set_last_query(&mut self, query: String);
201
202 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 fn set_results_as_datatable(&mut self, response: Option<QueryResponse>) -> Result<(), String>;
210
211 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 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 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 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 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 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 fn get_column_stats(&self) -> Option<&ColumnStatistics>;
268 fn set_column_stats(&mut self, stats: Option<ColumnStatistics>);
269
270 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 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 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 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 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 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 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 fn perform_undo(&mut self) -> bool;
334 fn perform_redo(&mut self) -> bool;
335 fn save_state_for_undo(&mut self);
336
337 fn get_last_visible_rows(&self) -> usize;
339 fn set_last_visible_rows(&mut self, rows: usize);
340
341 fn debug_dump(&self) -> String;
343
344 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 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 fn clear_results(&mut self);
360}
361
362pub struct Buffer {
364 pub id: usize,
366
367 pub file_path: Option<PathBuf>,
369
370 pub name: String,
372
373 pub modified: bool,
375
376 pub datatable: Option<Arc<DataTable>>,
378 pub original_source: Option<Arc<DataTable>>,
380 pub dataview: Option<DataView>,
382
383 pub mode: AppMode,
385 pub edit_mode: EditMode,
386 pub input: Input, pub input_manager: Box<dyn InputManager>, pub table_state: TableState,
389 pub last_results_row: Option<usize>,
390 pub last_scroll_offset: (usize, usize),
391
392 pub last_query: String,
394 pub status_message: String,
395
396 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 pub view_state: ViewState,
406
407 pub column_widths: Vec<u16>,
409 pub compact_mode: bool,
410 pub show_row_numbers: bool,
411 pub case_insensitive: bool,
412
413 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 pub highlighted_text_cache: Option<Vec<(String, Color)>>, pub last_highlighted_text: String, pub saved_input_state: Option<(String, usize)>, }
427
428impl BufferAPI for Buffer {
430 fn get_id(&self) -> usize {
432 self.id
433 }
434
435 fn get_query(&self) -> String {
437 self.input_manager.get_text()
439 }
440
441 fn set_query(&mut self, query: String) {
442 self.input_manager.set_text(query.clone());
444 self.input = Input::new(query.clone()).with_cursor(query.len());
445 }
446
447 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 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 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 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 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 if let Some(dt) = &datatable {
515 let mut view = crate::data::data_view::DataView::new(dt.clone());
517
518 if let Some(old_view) = &self.dataview {
520 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 view.hide_column_by_name(col_name);
528 }
529 }
530 }
531
532 view.shrink_to_fit();
534
535 self.dataview = Some(view);
537 } else {
538 self.dataview = None;
539 }
540
541 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 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 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 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 fn get_selected_row(&self) -> Option<usize> {
634 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 self.table_state.select(Some(r));
655 } else {
656 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 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 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 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 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 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 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 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 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 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 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 fn apply_filter(&mut self) -> Result<()> {
923 Ok(())
925 }
926
927 fn apply_sort(&mut self) -> Result<()> {
928 Ok(())
930 }
931
932 fn search(&mut self) -> Result<()> {
933 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 }
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 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 let current_state = (self.get_input_text(), self.get_input_cursor_position());
1005 self.push_redo(current_state);
1006
1007 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 let current_state = (self.get_input_text(), self.get_input_cursor_position());
1020 self.push_undo(current_state);
1021
1022 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 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>" ));
1127 output.push_str(&format!(
1128 "Matching Columns: {:?}\n",
1129 Vec::<(usize, String)>::new() ));
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 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 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 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 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 self.edit_mode = EditMode::SingleLine;
1205 self.input_manager = create_single_line(current_text.clone());
1206 self.input =
1208 Input::new(current_text.clone()).with_cursor(cursor_pos.min(current_text.len()));
1209
1210 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 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 fn navigate_history_up(&mut self, history: &[String]) -> bool {
1233 self.input_manager.set_history(history.to_vec());
1235 let navigated = self.input_manager.history_previous();
1236 if navigated {
1237 self.sync_from_input_manager();
1239 }
1240 navigated
1241 }
1242
1243 fn navigate_history_down(&mut self, history: &[String]) -> bool {
1244 self.input_manager.set_history(history.to_vec());
1246 let navigated = self.input_manager.history_next();
1247 if navigated {
1248 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 fn clear_results(&mut self) {
1260 self.datatable = None;
1261 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 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 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 #[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 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_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 #[must_use]
1345 pub fn from_csv(
1346 id: usize,
1347 path: PathBuf,
1348 _csv_client: CsvApiClient, _table_name: String, ) -> 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 buffer
1363 }
1364
1365 #[must_use]
1367 pub fn from_json(
1368 id: usize,
1369 path: PathBuf,
1370 _csv_client: CsvApiClient, _table_name: String, ) -> 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 buffer
1385 }
1386
1387 pub fn display_name(&self) -> String {
1389 if self.modified {
1390 format!("{}*", self.name)
1391 } else {
1392 self.name.clone()
1393 }
1394 }
1395
1396 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 pub fn has_file(&self, path: &PathBuf) -> bool {
1408 self.file_path.as_ref() == Some(path)
1409 }
1410
1411 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 let text_len = text.len();
1418 self.input = Input::new(text).with_cursor(cursor_pos.min(text_len));
1419 }
1420
1421 fn sync_to_input_manager(&mut self) {
1423 let _text = self.input.value().to_string();
1425 self.input_manager = create_from_input(self.input.clone());
1426 }
1427
1428 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let columns_str = columns.join(", ");
1578
1579 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 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 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 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 if let Some(dataview) = &self.dataview {
1614 let visible_columns = dataview.get_display_column_names();
1615
1616 if !visible_columns.is_empty() {
1617 let columns_str = visible_columns.join(", ");
1619
1620 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 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
1646impl Clone for Buffer {
1648 fn clone(&self) -> Self {
1649 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 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_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
1693pub 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 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 #[must_use]
1729 pub fn current(&self) -> Option<&Buffer> {
1730 self.buffers.get(self.current_buffer_index)
1731 }
1732
1733 pub fn current_mut(&mut self) -> Option<&mut Buffer> {
1735 self.buffers.get_mut(self.current_buffer_index)
1736 }
1737
1738 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 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 pub fn switch_to(&mut self, index: usize) {
1758 if index < self.buffers.len() {
1759 self.current_buffer_index = index;
1760 }
1761 }
1762
1763 pub fn close_current(&mut self) -> bool {
1765 if self.buffers.len() <= 1 {
1766 return false; }
1768
1769 self.buffers.remove(self.current_buffer_index);
1770
1771 if self.current_buffer_index >= self.buffers.len() {
1773 self.current_buffer_index = self.buffers.len() - 1;
1774 }
1775
1776 true
1777 }
1778
1779 #[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 #[must_use]
1787 pub fn all_buffers(&self) -> &[Buffer] {
1788 &self.buffers
1789 }
1790
1791 #[must_use]
1793 pub fn current_index(&self) -> usize {
1794 self.current_buffer_index
1795 }
1796
1797 #[must_use]
1799 pub fn has_multiple(&self) -> bool {
1800 self.buffers.len() > 1
1801 }
1802
1803 pub fn clear_all(&mut self) {
1805 self.buffers.clear();
1806 self.current_buffer_index = 0;
1807 }
1808}