ricecoder_tui/
components.rs

1//! Interactive UI components
2
3use crate::app::AppMode;
4
5/// Mode indicator component
6#[derive(Debug, Clone)]
7pub struct ModeIndicator {
8    /// Current mode
9    pub mode: AppMode,
10    /// Show keyboard shortcut
11    pub show_shortcut: bool,
12    /// Show mode capabilities
13    pub show_capabilities: bool,
14}
15
16impl ModeIndicator {
17    /// Create a new mode indicator
18    pub fn new(mode: AppMode) -> Self {
19        Self {
20            mode,
21            show_shortcut: true,
22            show_capabilities: false,
23        }
24    }
25
26    /// Get the display text for the mode
27    pub fn display_text(&self) -> String {
28        if self.show_shortcut {
29            format!("[{}] {}", self.mode.shortcut(), self.mode.display_name())
30        } else {
31            format!("[{}]", self.mode.display_name())
32        }
33    }
34
35    /// Get the short display text
36    pub fn short_text(&self) -> &'static str {
37        self.mode.display_name()
38    }
39
40    /// Get the capabilities for the current mode
41    pub fn get_capabilities(&self) -> Vec<&'static str> {
42        match self.mode {
43            AppMode::Chat => vec!["QuestionAnswering", "FreeformChat"],
44            AppMode::Command => vec!["CodeGeneration", "FileOperations", "CommandExecution"],
45            AppMode::Diff => vec!["CodeModification", "FileOperations"],
46            AppMode::Help => vec!["QuestionAnswering"],
47        }
48    }
49
50    /// Get capabilities display text
51    pub fn capabilities_text(&self) -> String {
52        let caps = self.get_capabilities();
53        format!("Capabilities: {}", caps.join(", "))
54    }
55
56    /// Update the mode
57    pub fn set_mode(&mut self, mode: AppMode) {
58        self.mode = mode;
59    }
60
61    /// Toggle shortcut display
62    pub fn toggle_shortcut_display(&mut self) {
63        self.show_shortcut = !self.show_shortcut;
64    }
65
66    /// Toggle capabilities display
67    pub fn toggle_capabilities_display(&mut self) {
68        self.show_capabilities = !self.show_capabilities;
69    }
70
71    /// Enable capabilities display
72    pub fn show_capabilities_enabled(&mut self) {
73        self.show_capabilities = true;
74    }
75
76    /// Disable capabilities display
77    pub fn hide_capabilities_enabled(&mut self) {
78        self.show_capabilities = false;
79    }
80}
81
82impl Default for ModeIndicator {
83    fn default() -> Self {
84        Self::new(AppMode::Chat)
85    }
86}
87
88/// Mode selection menu for switching between modes
89#[derive(Debug, Clone)]
90pub struct ModeSelectionMenu {
91    /// Available modes
92    pub modes: Vec<AppMode>,
93    /// Currently selected mode index
94    pub selected: usize,
95    /// Whether the menu is open
96    pub open: bool,
97    /// Whether to show confirmation dialog
98    pub show_confirmation: bool,
99    /// Previous mode (for cancellation)
100    pub previous_mode: AppMode,
101}
102
103impl ModeSelectionMenu {
104    /// Create a new mode selection menu
105    pub fn new() -> Self {
106        Self {
107            modes: vec![
108                AppMode::Chat,
109                AppMode::Command,
110                AppMode::Diff,
111                AppMode::Help,
112            ],
113            selected: 0,
114            open: false,
115            show_confirmation: false,
116            previous_mode: AppMode::Chat,
117        }
118    }
119
120    /// Open the mode selection menu
121    pub fn open(&mut self, current_mode: AppMode) {
122        self.open = true;
123        self.previous_mode = current_mode;
124        // Find and select the current mode
125        if let Some(pos) = self.modes.iter().position(|&m| m == current_mode) {
126            self.selected = pos;
127        }
128    }
129
130    /// Close the mode selection menu
131    pub fn close(&mut self) {
132        self.open = false;
133        self.show_confirmation = false;
134    }
135
136    /// Get the currently selected mode
137    pub fn selected_mode(&self) -> AppMode {
138        self.modes
139            .get(self.selected)
140            .copied()
141            .unwrap_or(AppMode::Chat)
142    }
143
144    /// Move selection to next mode
145    pub fn select_next(&mut self) {
146        if self.selected < self.modes.len().saturating_sub(1) {
147            self.selected += 1;
148        } else {
149            self.selected = 0;
150        }
151    }
152
153    /// Move selection to previous mode
154    pub fn select_prev(&mut self) {
155        if self.selected > 0 {
156            self.selected -= 1;
157        } else {
158            self.selected = self.modes.len().saturating_sub(1);
159        }
160    }
161
162    /// Confirm mode switch
163    pub fn confirm_switch(&mut self) -> AppMode {
164        let mode = self.selected_mode();
165        self.close();
166        mode
167    }
168
169    /// Cancel mode switch
170    pub fn cancel_switch(&mut self) {
171        self.close();
172    }
173
174    /// Get mode descriptions for display
175    pub fn get_mode_descriptions(&self) -> Vec<(&AppMode, &'static str)> {
176        self.modes
177            .iter()
178            .map(|mode| {
179                let desc = match mode {
180                    AppMode::Chat => "Chat with the AI assistant",
181                    AppMode::Command => "Execute commands and generate code",
182                    AppMode::Diff => "Review and apply code changes",
183                    AppMode::Help => "Get help and documentation",
184                };
185                (mode, desc)
186            })
187            .collect()
188    }
189
190    /// Get keyboard shortcuts for mode switching
191    pub fn get_shortcuts(&self) -> Vec<(&'static str, &'static str)> {
192        vec![
193            ("Ctrl+1", "Chat Mode"),
194            ("Ctrl+2", "Command Mode"),
195            ("Ctrl+3", "Diff Mode"),
196            ("Ctrl+4", "Help Mode"),
197        ]
198    }
199}
200
201impl Default for ModeSelectionMenu {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207/// Menu item
208#[derive(Debug, Clone)]
209pub struct MenuItem {
210    /// Item label
211    pub label: String,
212    /// Item description
213    pub description: Option<String>,
214}
215
216impl MenuItem {
217    /// Create a new menu item
218    pub fn new(label: impl Into<String>) -> Self {
219        Self {
220            label: label.into(),
221            description: None,
222        }
223    }
224
225    /// Set description
226    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
227        self.description = Some(desc.into());
228        self
229    }
230}
231
232/// Menu widget
233pub struct MenuWidget {
234    /// Menu items
235    pub items: Vec<MenuItem>,
236    /// Selected item index
237    pub selected: usize,
238    /// Whether menu is open
239    pub open: bool,
240    /// Menu title
241    pub title: Option<String>,
242    /// Scroll offset for large menus
243    pub scroll: usize,
244}
245
246impl MenuWidget {
247    /// Create a new menu widget
248    pub fn new() -> Self {
249        Self {
250            items: Vec::new(),
251            selected: 0,
252            open: false,
253            title: None,
254            scroll: 0,
255        }
256    }
257
258    /// Create a menu with a title
259    pub fn with_title(title: impl Into<String>) -> Self {
260        Self {
261            items: Vec::new(),
262            selected: 0,
263            open: false,
264            title: Some(title.into()),
265            scroll: 0,
266        }
267    }
268
269    /// Add a menu item
270    pub fn add_item(&mut self, item: MenuItem) {
271        self.items.push(item);
272    }
273
274    /// Add multiple items
275    pub fn add_items(&mut self, items: Vec<MenuItem>) {
276        self.items.extend(items);
277    }
278
279    /// Select next item
280    pub fn select_next(&mut self) {
281        if self.selected < self.items.len().saturating_sub(1) {
282            self.selected += 1;
283            self.ensure_visible(10); // Assume 10 visible items
284        }
285    }
286
287    /// Select previous item
288    pub fn select_prev(&mut self) {
289        if self.selected > 0 {
290            self.selected -= 1;
291            self.ensure_visible(10);
292        }
293    }
294
295    /// Jump to first item
296    pub fn select_first(&mut self) {
297        self.selected = 0;
298        self.scroll = 0;
299    }
300
301    /// Jump to last item
302    pub fn select_last(&mut self) {
303        self.selected = self.items.len().saturating_sub(1);
304        self.ensure_visible(10);
305    }
306
307    /// Ensure selected item is visible
308    fn ensure_visible(&mut self, visible_height: usize) {
309        if self.selected < self.scroll {
310            self.scroll = self.selected;
311        } else if self.selected >= self.scroll + visible_height {
312            self.scroll = self.selected.saturating_sub(visible_height - 1);
313        }
314    }
315
316    /// Get selected item
317    pub fn selected_item(&self) -> Option<&MenuItem> {
318        self.items.get(self.selected)
319    }
320
321    /// Get selected item index
322    pub fn selected_index(&self) -> usize {
323        self.selected
324    }
325
326    /// Open the menu
327    pub fn open(&mut self) {
328        self.open = true;
329    }
330
331    /// Close the menu
332    pub fn close(&mut self) {
333        self.open = false;
334    }
335
336    /// Toggle menu open state
337    pub fn toggle(&mut self) {
338        self.open = !self.open;
339    }
340
341    /// Get visible items based on scroll
342    pub fn visible_items(&self, height: usize) -> Vec<(usize, &MenuItem)> {
343        self.items
344            .iter()
345            .enumerate()
346            .skip(self.scroll)
347            .take(height)
348            .collect()
349    }
350
351    /// Clear all items
352    pub fn clear(&mut self) {
353        self.items.clear();
354        self.selected = 0;
355        self.scroll = 0;
356    }
357
358    /// Get total item count
359    pub fn item_count(&self) -> usize {
360        self.items.len()
361    }
362
363    /// Check if menu is empty
364    pub fn is_empty(&self) -> bool {
365        self.items.is_empty()
366    }
367}
368
369impl Default for MenuWidget {
370    fn default() -> Self {
371        Self::new()
372    }
373}
374
375/// List widget
376pub struct ListWidget {
377    /// List items
378    pub items: Vec<String>,
379    /// Selected item index
380    pub selected: Option<usize>,
381    /// Filter text
382    pub filter: String,
383    /// Multi-select enabled
384    pub multi_select: bool,
385    /// Selected items (for multi-select)
386    pub selected_items: std::collections::HashSet<usize>,
387    /// Scroll offset
388    pub scroll: usize,
389}
390
391impl ListWidget {
392    /// Create a new list widget
393    pub fn new() -> Self {
394        Self {
395            items: Vec::new(),
396            selected: None,
397            filter: String::new(),
398            multi_select: false,
399            selected_items: std::collections::HashSet::new(),
400            scroll: 0,
401        }
402    }
403
404    /// Enable multi-select mode
405    pub fn with_multi_select(mut self) -> Self {
406        self.multi_select = true;
407        self
408    }
409
410    /// Add an item
411    pub fn add_item(&mut self, item: impl Into<String>) {
412        self.items.push(item.into());
413    }
414
415    /// Add multiple items
416    pub fn add_items(&mut self, items: Vec<String>) {
417        self.items.extend(items);
418    }
419
420    /// Set filter
421    pub fn set_filter(&mut self, filter: impl Into<String>) {
422        self.filter = filter.into();
423        self.scroll = 0; // Reset scroll when filtering
424    }
425
426    /// Clear filter
427    pub fn clear_filter(&mut self) {
428        self.filter.clear();
429        self.scroll = 0;
430    }
431
432    /// Get filtered items
433    pub fn filtered_items(&self) -> Vec<(usize, &String)> {
434        self.items
435            .iter()
436            .enumerate()
437            .filter(|(_, item)| item.to_lowercase().contains(&self.filter.to_lowercase()))
438            .collect()
439    }
440
441    /// Get visible items based on scroll
442    pub fn visible_items(&self, height: usize) -> Vec<(usize, &String)> {
443        self.filtered_items()
444            .into_iter()
445            .skip(self.scroll)
446            .take(height)
447            .collect()
448    }
449
450    /// Select next item
451    pub fn select_next(&mut self) {
452        let filtered = self.filtered_items();
453        if filtered.is_empty() {
454            return;
455        }
456
457        match self.selected {
458            None => {
459                self.selected = Some(filtered[0].0);
460                self.scroll = 0;
461            }
462            Some(idx) => {
463                if let Some(pos) = filtered.iter().position(|(i, _)| *i == idx) {
464                    if pos < filtered.len() - 1 {
465                        self.selected = Some(filtered[pos + 1].0);
466                    }
467                }
468            }
469        }
470    }
471
472    /// Select previous item
473    pub fn select_prev(&mut self) {
474        let filtered = self.filtered_items();
475        if filtered.is_empty() {
476            return;
477        }
478
479        match self.selected {
480            None => {}
481            Some(idx) => {
482                if let Some(pos) = filtered.iter().position(|(i, _)| *i == idx) {
483                    if pos > 0 {
484                        self.selected = Some(filtered[pos - 1].0);
485                    }
486                }
487            }
488        }
489    }
490
491    /// Toggle selection for current item (multi-select)
492    pub fn toggle_selection(&mut self) {
493        if self.multi_select {
494            if let Some(idx) = self.selected {
495                if self.selected_items.contains(&idx) {
496                    self.selected_items.remove(&idx);
497                } else {
498                    self.selected_items.insert(idx);
499                }
500            }
501        }
502    }
503
504    /// Select all items
505    pub fn select_all(&mut self) {
506        if self.multi_select {
507            let indices: Vec<usize> = self
508                .filtered_items()
509                .into_iter()
510                .map(|(idx, _)| idx)
511                .collect();
512            for idx in indices {
513                self.selected_items.insert(idx);
514            }
515        }
516    }
517
518    /// Deselect all items
519    pub fn deselect_all(&mut self) {
520        self.selected_items.clear();
521    }
522
523    /// Get selected item
524    pub fn selected_item(&self) -> Option<&String> {
525        self.selected.and_then(|idx| self.items.get(idx))
526    }
527
528    /// Get all selected items (multi-select)
529    pub fn get_selected_items(&self) -> Vec<&String> {
530        self.selected_items
531            .iter()
532            .filter_map(|idx| self.items.get(*idx))
533            .collect()
534    }
535
536    /// Clear all items
537    pub fn clear(&mut self) {
538        self.items.clear();
539        self.selected = None;
540        self.selected_items.clear();
541        self.scroll = 0;
542    }
543
544    /// Get total item count
545    pub fn item_count(&self) -> usize {
546        self.items.len()
547    }
548
549    /// Check if list is empty
550    pub fn is_empty(&self) -> bool {
551        self.items.is_empty()
552    }
553}
554
555impl Default for ListWidget {
556    fn default() -> Self {
557        Self::new()
558    }
559}
560
561/// Dialog type
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
563pub enum DialogType {
564    /// Input dialog
565    Input,
566    /// Confirmation dialog
567    Confirm,
568    /// Message dialog
569    Message,
570}
571
572/// Dialog result
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574pub enum DialogResult {
575    /// Dialog was confirmed
576    Confirmed,
577    /// Dialog was cancelled
578    Cancelled,
579    /// Dialog is still open
580    Pending,
581}
582
583/// Dialog widget
584pub struct DialogWidget {
585    /// Dialog type
586    pub dialog_type: DialogType,
587    /// Dialog title
588    pub title: String,
589    /// Dialog message
590    pub message: String,
591    /// Input value (for input dialogs)
592    pub input: String,
593    /// Cursor position
594    pub cursor: usize,
595    /// Dialog result
596    pub result: DialogResult,
597    /// Validation function (for input dialogs)
598    pub validator: Option<fn(&str) -> bool>,
599    /// Error message (if validation fails)
600    pub error_message: Option<String>,
601    /// Confirmation state (for confirm dialogs)
602    pub confirmed: Option<bool>,
603}
604
605impl DialogWidget {
606    /// Create a new dialog widget
607    pub fn new(
608        dialog_type: DialogType,
609        title: impl Into<String>,
610        message: impl Into<String>,
611    ) -> Self {
612        Self {
613            dialog_type,
614            title: title.into(),
615            message: message.into(),
616            input: String::new(),
617            cursor: 0,
618            result: DialogResult::Pending,
619            validator: None,
620            error_message: None,
621            confirmed: None,
622        }
623    }
624
625    /// Set a validator function
626    pub fn with_validator(mut self, validator: fn(&str) -> bool) -> Self {
627        self.validator = Some(validator);
628        self
629    }
630
631    /// Insert character
632    pub fn insert_char(&mut self, ch: char) {
633        if ch.is_ascii_graphic() || ch == ' ' {
634            self.input.insert(self.cursor, ch);
635            self.cursor += 1;
636            self.error_message = None;
637        }
638    }
639
640    /// Backspace
641    pub fn backspace(&mut self) {
642        if self.cursor > 0 {
643            self.input.remove(self.cursor - 1);
644            self.cursor -= 1;
645            self.error_message = None;
646        }
647    }
648
649    /// Delete character at cursor
650    pub fn delete(&mut self) {
651        if self.cursor < self.input.len() {
652            self.input.remove(self.cursor);
653        }
654    }
655
656    /// Move cursor left
657    pub fn cursor_left(&mut self) {
658        if self.cursor > 0 {
659            self.cursor -= 1;
660        }
661    }
662
663    /// Move cursor right
664    pub fn cursor_right(&mut self) {
665        if self.cursor < self.input.len() {
666            self.cursor += 1;
667        }
668    }
669
670    /// Move cursor to start
671    pub fn cursor_start(&mut self) {
672        self.cursor = 0;
673    }
674
675    /// Move cursor to end
676    pub fn cursor_end(&mut self) {
677        self.cursor = self.input.len();
678    }
679
680    /// Get input value
681    pub fn get_input(&self) -> String {
682        self.input.clone()
683    }
684
685    /// Validate input
686    pub fn validate(&mut self) -> bool {
687        if let Some(validator) = self.validator {
688            if validator(&self.input) {
689                self.error_message = None;
690                true
691            } else {
692                self.error_message = Some("Invalid input".to_string());
693                false
694            }
695        } else {
696            true
697        }
698    }
699
700    /// Confirm dialog
701    pub fn confirm(&mut self) {
702        match self.dialog_type {
703            DialogType::Input => {
704                if self.validate() {
705                    self.result = DialogResult::Confirmed;
706                }
707            }
708            DialogType::Confirm => {
709                self.confirmed = Some(true);
710                self.result = DialogResult::Confirmed;
711            }
712            DialogType::Message => {
713                self.result = DialogResult::Confirmed;
714            }
715        }
716    }
717
718    /// Cancel dialog
719    pub fn cancel(&mut self) {
720        if self.dialog_type == DialogType::Confirm {
721            self.confirmed = Some(false);
722        }
723        self.result = DialogResult::Cancelled;
724    }
725
726    /// Check if dialog is confirmed
727    pub fn is_confirmed(&self) -> bool {
728        self.result == DialogResult::Confirmed
729    }
730
731    /// Check if dialog is cancelled
732    pub fn is_cancelled(&self) -> bool {
733        self.result == DialogResult::Cancelled
734    }
735
736    /// Check if dialog is pending
737    pub fn is_pending(&self) -> bool {
738        self.result == DialogResult::Pending
739    }
740
741    /// Clear input
742    pub fn clear_input(&mut self) {
743        self.input.clear();
744        self.cursor = 0;
745        self.error_message = None;
746    }
747}
748
749/// Split direction
750#[derive(Debug, Clone, Copy, PartialEq, Eq)]
751pub enum SplitDirection {
752    /// Vertical split (left/right)
753    Vertical,
754    /// Horizontal split (top/bottom)
755    Horizontal,
756}
757
758/// Split view widget
759pub struct SplitViewWidget {
760    /// Left/top panel content
761    pub left_content: String,
762    /// Right/bottom panel content
763    pub right_content: String,
764    /// Split ratio (0-100)
765    pub split_ratio: u8,
766    /// Split direction
767    pub direction: SplitDirection,
768    /// Active panel (0 = left/top, 1 = right/bottom)
769    pub active_panel: usize,
770    /// Left/top panel scroll
771    pub left_scroll: usize,
772    /// Right/bottom panel scroll
773    pub right_scroll: usize,
774}
775
776impl SplitViewWidget {
777    /// Create a new split view widget
778    pub fn new() -> Self {
779        Self {
780            left_content: String::new(),
781            right_content: String::new(),
782            split_ratio: 50,
783            direction: SplitDirection::Vertical,
784            active_panel: 0,
785            left_scroll: 0,
786            right_scroll: 0,
787        }
788    }
789
790    /// Create a horizontal split view
791    pub fn horizontal() -> Self {
792        Self {
793            left_content: String::new(),
794            right_content: String::new(),
795            split_ratio: 50,
796            direction: SplitDirection::Horizontal,
797            active_panel: 0,
798            left_scroll: 0,
799            right_scroll: 0,
800        }
801    }
802
803    /// Set left/top content
804    pub fn set_left(&mut self, content: impl Into<String>) {
805        self.left_content = content.into();
806    }
807
808    /// Set right/bottom content
809    pub fn set_right(&mut self, content: impl Into<String>) {
810        self.right_content = content.into();
811    }
812
813    /// Adjust split ratio
814    pub fn adjust_split(&mut self, delta: i8) {
815        let new_ratio = (self.split_ratio as i16 + delta as i16).clamp(20, 80) as u8;
816        self.split_ratio = new_ratio;
817    }
818
819    /// Switch active panel
820    pub fn switch_panel(&mut self) {
821        self.active_panel = 1 - self.active_panel;
822    }
823
824    /// Get active panel content
825    pub fn active_content(&self) -> &str {
826        if self.active_panel == 0 {
827            &self.left_content
828        } else {
829            &self.right_content
830        }
831    }
832
833    /// Get active panel scroll
834    pub fn active_scroll(&self) -> usize {
835        if self.active_panel == 0 {
836            self.left_scroll
837        } else {
838            self.right_scroll
839        }
840    }
841
842    /// Scroll active panel up
843    pub fn scroll_up(&mut self) {
844        if self.active_panel == 0 {
845            if self.left_scroll > 0 {
846                self.left_scroll -= 1;
847            }
848        } else if self.right_scroll > 0 {
849            self.right_scroll -= 1;
850        }
851    }
852
853    /// Scroll active panel down
854    pub fn scroll_down(&mut self) {
855        if self.active_panel == 0 {
856            self.left_scroll += 1;
857        } else {
858            self.right_scroll += 1;
859        }
860    }
861}
862
863impl Default for SplitViewWidget {
864    fn default() -> Self {
865        Self::new()
866    }
867}
868
869/// Tab widget
870pub struct TabWidget {
871    /// Tab titles
872    pub tabs: Vec<String>,
873    /// Active tab index
874    pub active: usize,
875    /// Tab content
876    pub content: Vec<String>,
877    /// Scroll offset for tab bar
878    pub scroll: usize,
879}
880
881impl TabWidget {
882    /// Create a new tab widget
883    pub fn new() -> Self {
884        Self {
885            tabs: Vec::new(),
886            active: 0,
887            content: Vec::new(),
888            scroll: 0,
889        }
890    }
891
892    /// Add a tab
893    pub fn add_tab(&mut self, title: impl Into<String>) {
894        self.tabs.push(title.into());
895        self.content.push(String::new());
896    }
897
898    /// Add a tab with content
899    pub fn add_tab_with_content(&mut self, title: impl Into<String>, content: impl Into<String>) {
900        self.tabs.push(title.into());
901        self.content.push(content.into());
902    }
903
904    /// Select next tab
905    pub fn select_next(&mut self) {
906        if self.active < self.tabs.len().saturating_sub(1) {
907            self.active += 1;
908            self.ensure_visible(10);
909        }
910    }
911
912    /// Select previous tab
913    pub fn select_prev(&mut self) {
914        if self.active > 0 {
915            self.active -= 1;
916            self.ensure_visible(10);
917        }
918    }
919
920    /// Select tab by index
921    pub fn select_tab(&mut self, index: usize) {
922        if index < self.tabs.len() {
923            self.active = index;
924            self.ensure_visible(10);
925        }
926    }
927
928    /// Ensure active tab is visible
929    fn ensure_visible(&mut self, visible_width: usize) {
930        if self.active < self.scroll {
931            self.scroll = self.active;
932        } else if self.active >= self.scroll + visible_width {
933            self.scroll = self.active.saturating_sub(visible_width - 1);
934        }
935    }
936
937    /// Get active tab title
938    pub fn active_tab(&self) -> Option<&String> {
939        self.tabs.get(self.active)
940    }
941
942    /// Get active tab content
943    pub fn active_content(&self) -> Option<&String> {
944        self.content.get(self.active)
945    }
946
947    /// Set content for active tab
948    pub fn set_active_content(&mut self, content: impl Into<String>) {
949        if let Some(c) = self.content.get_mut(self.active) {
950            *c = content.into();
951        }
952    }
953
954    /// Get visible tabs
955    pub fn visible_tabs(&self, width: usize) -> Vec<(usize, &String)> {
956        self.tabs
957            .iter()
958            .enumerate()
959            .skip(self.scroll)
960            .take(width)
961            .collect()
962    }
963
964    /// Close tab
965    pub fn close_tab(&mut self, index: usize) {
966        if index < self.tabs.len() {
967            self.tabs.remove(index);
968            self.content.remove(index);
969
970            if self.active >= self.tabs.len() && self.active > 0 {
971                self.active -= 1;
972            }
973        }
974    }
975
976    /// Close active tab
977    pub fn close_active_tab(&mut self) {
978        self.close_tab(self.active);
979    }
980
981    /// Get tab count
982    pub fn tab_count(&self) -> usize {
983        self.tabs.len()
984    }
985
986    /// Check if tabs are empty
987    pub fn is_empty(&self) -> bool {
988        self.tabs.is_empty()
989    }
990
991    /// Clear all tabs
992    pub fn clear(&mut self) {
993        self.tabs.clear();
994        self.content.clear();
995        self.active = 0;
996        self.scroll = 0;
997    }
998}
999
1000impl Default for TabWidget {
1001    fn default() -> Self {
1002        Self::new()
1003    }
1004}
1005
1006/// Vim keybinding mode
1007#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1008pub enum VimMode {
1009    /// Normal mode (navigation)
1010    Normal,
1011    /// Insert mode (text input)
1012    Insert,
1013    /// Visual mode (selection)
1014    Visual,
1015    /// Command mode (commands)
1016    Command,
1017}
1018
1019/// Vim keybindings configuration
1020pub struct VimKeybindings {
1021    /// Whether vim mode is enabled
1022    pub enabled: bool,
1023    /// Current vim mode
1024    pub mode: VimMode,
1025    /// Command buffer for command mode
1026    pub command_buffer: String,
1027}
1028
1029impl VimKeybindings {
1030    /// Create a new vim keybindings configuration
1031    pub fn new() -> Self {
1032        Self {
1033            enabled: false,
1034            mode: VimMode::Normal,
1035            command_buffer: String::new(),
1036        }
1037    }
1038
1039    /// Enable vim mode
1040    pub fn enable(&mut self) {
1041        self.enabled = true;
1042        self.mode = VimMode::Normal;
1043    }
1044
1045    /// Disable vim mode
1046    pub fn disable(&mut self) {
1047        self.enabled = false;
1048        self.mode = VimMode::Normal;
1049        self.command_buffer.clear();
1050    }
1051
1052    /// Toggle vim mode
1053    pub fn toggle(&mut self) {
1054        if self.enabled {
1055            self.disable();
1056        } else {
1057            self.enable();
1058        }
1059    }
1060
1061    /// Enter insert mode
1062    pub fn enter_insert(&mut self) {
1063        if self.enabled {
1064            self.mode = VimMode::Insert;
1065        }
1066    }
1067
1068    /// Enter normal mode
1069    pub fn enter_normal(&mut self) {
1070        if self.enabled {
1071            self.mode = VimMode::Normal;
1072            self.command_buffer.clear();
1073        }
1074    }
1075
1076    /// Enter visual mode
1077    pub fn enter_visual(&mut self) {
1078        if self.enabled {
1079            self.mode = VimMode::Visual;
1080        }
1081    }
1082
1083    /// Enter command mode
1084    pub fn enter_command(&mut self) {
1085        if self.enabled {
1086            self.mode = VimMode::Command;
1087            self.command_buffer.clear();
1088        }
1089    }
1090
1091    /// Add character to command buffer
1092    pub fn add_to_command(&mut self, ch: char) {
1093        self.command_buffer.push(ch);
1094    }
1095
1096    /// Clear command buffer
1097    pub fn clear_command(&mut self) {
1098        self.command_buffer.clear();
1099    }
1100
1101    /// Get command buffer
1102    pub fn get_command(&self) -> &str {
1103        &self.command_buffer
1104    }
1105
1106    /// Check if in normal mode
1107    pub fn is_normal(&self) -> bool {
1108        self.enabled && self.mode == VimMode::Normal
1109    }
1110
1111    /// Check if in insert mode
1112    pub fn is_insert(&self) -> bool {
1113        self.enabled && self.mode == VimMode::Insert
1114    }
1115
1116    /// Check if in visual mode
1117    pub fn is_visual(&self) -> bool {
1118        self.enabled && self.mode == VimMode::Visual
1119    }
1120
1121    /// Check if in command mode
1122    pub fn is_command(&self) -> bool {
1123        self.enabled && self.mode == VimMode::Command
1124    }
1125}
1126
1127impl Default for VimKeybindings {
1128    fn default() -> Self {
1129        Self::new()
1130    }
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135    use super::*;
1136
1137    #[test]
1138    fn test_menu_widget() {
1139        let mut menu = MenuWidget::new();
1140        menu.add_item(MenuItem::new("Option 1"));
1141        menu.add_item(MenuItem::new("Option 2"));
1142
1143        assert_eq!(menu.selected, 0);
1144        menu.select_next();
1145        assert_eq!(menu.selected, 1);
1146        menu.select_prev();
1147        assert_eq!(menu.selected, 0);
1148    }
1149
1150    #[test]
1151    fn test_menu_widget_with_title() {
1152        let menu = MenuWidget::with_title("Main Menu");
1153        assert_eq!(menu.title, Some("Main Menu".to_string()));
1154        assert!(!menu.open);
1155    }
1156
1157    #[test]
1158    fn test_menu_widget_open_close() {
1159        let mut menu = MenuWidget::new();
1160        assert!(!menu.open);
1161
1162        menu.open();
1163        assert!(menu.open);
1164
1165        menu.close();
1166        assert!(!menu.open);
1167
1168        menu.toggle();
1169        assert!(menu.open);
1170    }
1171
1172    #[test]
1173    fn test_menu_widget_first_last() {
1174        let mut menu = MenuWidget::new();
1175        menu.add_item(MenuItem::new("Item 1"));
1176        menu.add_item(MenuItem::new("Item 2"));
1177        menu.add_item(MenuItem::new("Item 3"));
1178        menu.add_item(MenuItem::new("Item 4"));
1179
1180        menu.select_last();
1181        assert_eq!(menu.selected, 3);
1182
1183        menu.select_first();
1184        assert_eq!(menu.selected, 0);
1185    }
1186
1187    #[test]
1188    fn test_menu_widget_visible_items() {
1189        let mut menu = MenuWidget::new();
1190        for i in 0..10 {
1191            menu.add_item(MenuItem::new(format!("Item {}", i)));
1192        }
1193
1194        let visible = menu.visible_items(5);
1195        assert_eq!(visible.len(), 5);
1196
1197        menu.scroll = 3;
1198        let visible = menu.visible_items(5);
1199        assert_eq!(visible.len(), 5);
1200        assert_eq!(visible[0].0, 3);
1201    }
1202
1203    #[test]
1204    fn test_menu_widget_clear() {
1205        let mut menu = MenuWidget::new();
1206        menu.add_item(MenuItem::new("Item 1"));
1207        menu.add_item(MenuItem::new("Item 2"));
1208        menu.selected = 1;
1209
1210        menu.clear();
1211        assert!(menu.items.is_empty());
1212        assert_eq!(menu.selected, 0);
1213        assert_eq!(menu.scroll, 0);
1214    }
1215
1216    #[test]
1217    fn test_menu_widget_add_items() {
1218        let mut menu = MenuWidget::new();
1219        let items = vec![
1220            MenuItem::new("Item 1"),
1221            MenuItem::new("Item 2"),
1222            MenuItem::new("Item 3"),
1223        ];
1224        menu.add_items(items);
1225
1226        assert_eq!(menu.item_count(), 3);
1227    }
1228
1229    #[test]
1230    fn test_menu_widget_is_empty() {
1231        let menu = MenuWidget::new();
1232        assert!(menu.is_empty());
1233
1234        let mut menu = MenuWidget::new();
1235        menu.add_item(MenuItem::new("Item"));
1236        assert!(!menu.is_empty());
1237    }
1238
1239    #[test]
1240    fn test_list_widget() {
1241        let mut list = ListWidget::new();
1242        list.add_item("apple");
1243        list.add_item("banana");
1244        list.add_item("cherry");
1245
1246        assert_eq!(list.filtered_items().len(), 3);
1247
1248        list.set_filter("app");
1249        assert_eq!(list.filtered_items().len(), 1);
1250    }
1251
1252    #[test]
1253    fn test_list_widget_selection() {
1254        let mut list = ListWidget::new();
1255        list.add_item("item1");
1256        list.add_item("item2");
1257        list.add_item("item3");
1258
1259        list.select_next();
1260        assert_eq!(list.selected, Some(0));
1261
1262        list.select_next();
1263        assert_eq!(list.selected, Some(1));
1264    }
1265
1266    #[test]
1267    fn test_list_widget_multi_select() {
1268        let mut list = ListWidget::new().with_multi_select();
1269        list.add_item("item1");
1270        list.add_item("item2");
1271        list.add_item("item3");
1272
1273        assert!(list.multi_select);
1274
1275        list.select_next();
1276        list.toggle_selection();
1277        assert!(list.selected_items.contains(&0));
1278
1279        list.select_next();
1280        list.toggle_selection();
1281        assert!(list.selected_items.contains(&1));
1282
1283        let selected = list.get_selected_items();
1284        assert_eq!(selected.len(), 2);
1285    }
1286
1287    #[test]
1288    fn test_list_widget_select_all() {
1289        let mut list = ListWidget::new().with_multi_select();
1290        list.add_item("item1");
1291        list.add_item("item2");
1292        list.add_item("item3");
1293
1294        list.select_all();
1295        assert_eq!(list.selected_items.len(), 3);
1296    }
1297
1298    #[test]
1299    fn test_list_widget_deselect_all() {
1300        let mut list = ListWidget::new().with_multi_select();
1301        list.add_item("item1");
1302        list.add_item("item2");
1303        list.add_item("item3");
1304
1305        list.select_all();
1306        assert_eq!(list.selected_items.len(), 3);
1307
1308        list.deselect_all();
1309        assert!(list.selected_items.is_empty());
1310    }
1311
1312    #[test]
1313    fn test_list_widget_filter() {
1314        let mut list = ListWidget::new();
1315        list.add_item("apple");
1316        list.add_item("apricot");
1317        list.add_item("banana");
1318        list.add_item("blueberry");
1319
1320        list.set_filter("ap");
1321        assert_eq!(list.filtered_items().len(), 2);
1322
1323        list.set_filter("b");
1324        assert_eq!(list.filtered_items().len(), 2);
1325
1326        list.clear_filter();
1327        assert_eq!(list.filtered_items().len(), 4);
1328    }
1329
1330    #[test]
1331    fn test_list_widget_visible_items() {
1332        let mut list = ListWidget::new();
1333        for i in 0..10 {
1334            list.add_item(format!("item{}", i));
1335        }
1336
1337        let visible = list.visible_items(5);
1338        assert_eq!(visible.len(), 5);
1339
1340        list.scroll = 3;
1341        let visible = list.visible_items(5);
1342        assert_eq!(visible.len(), 5);
1343    }
1344
1345    #[test]
1346    fn test_list_widget_add_items() {
1347        let mut list = ListWidget::new();
1348        let items = vec![
1349            "item1".to_string(),
1350            "item2".to_string(),
1351            "item3".to_string(),
1352        ];
1353        list.add_items(items);
1354
1355        assert_eq!(list.item_count(), 3);
1356    }
1357
1358    #[test]
1359    fn test_list_widget_clear() {
1360        let mut list = ListWidget::new();
1361        list.add_item("item1");
1362        list.add_item("item2");
1363        list.selected = Some(0);
1364
1365        list.clear();
1366        assert!(list.items.is_empty());
1367        assert!(list.selected.is_none());
1368    }
1369
1370    #[test]
1371    fn test_list_widget_is_empty() {
1372        let list = ListWidget::new();
1373        assert!(list.is_empty());
1374
1375        let mut list = ListWidget::new();
1376        list.add_item("item");
1377        assert!(!list.is_empty());
1378    }
1379
1380    #[test]
1381    fn test_dialog_widget() {
1382        let dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1383        assert_eq!(dialog.dialog_type, DialogType::Input);
1384        assert_eq!(dialog.title, "Title");
1385        assert!(dialog.is_pending());
1386    }
1387
1388    #[test]
1389    fn test_dialog_widget_input() {
1390        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1391        dialog.insert_char('h');
1392        dialog.insert_char('i');
1393
1394        assert_eq!(dialog.get_input(), "hi");
1395    }
1396
1397    #[test]
1398    fn test_dialog_widget_cursor_movement() {
1399        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1400        dialog.insert_char('h');
1401        dialog.insert_char('e');
1402        dialog.insert_char('l');
1403        dialog.insert_char('l');
1404        dialog.insert_char('o');
1405
1406        assert_eq!(dialog.cursor, 5);
1407
1408        dialog.cursor_left();
1409        assert_eq!(dialog.cursor, 4);
1410
1411        dialog.cursor_right();
1412        assert_eq!(dialog.cursor, 5);
1413
1414        dialog.cursor_start();
1415        assert_eq!(dialog.cursor, 0);
1416
1417        dialog.cursor_end();
1418        assert_eq!(dialog.cursor, 5);
1419    }
1420
1421    #[test]
1422    fn test_dialog_widget_delete() {
1423        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1424        dialog.insert_char('h');
1425        dialog.insert_char('e');
1426        dialog.insert_char('l');
1427        dialog.insert_char('l');
1428        dialog.insert_char('o');
1429
1430        dialog.cursor_start();
1431        dialog.delete();
1432        assert_eq!(dialog.get_input(), "ello");
1433    }
1434
1435    #[test]
1436    fn test_dialog_widget_backspace() {
1437        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1438        dialog.insert_char('h');
1439        dialog.insert_char('e');
1440        dialog.insert_char('l');
1441        dialog.insert_char('l');
1442        dialog.insert_char('o');
1443
1444        dialog.backspace();
1445        assert_eq!(dialog.get_input(), "hell");
1446        assert_eq!(dialog.cursor, 4);
1447    }
1448
1449    #[test]
1450    fn test_dialog_widget_confirm() {
1451        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1452        dialog.insert_char('t');
1453        dialog.insert_char('e');
1454        dialog.insert_char('s');
1455        dialog.insert_char('t');
1456
1457        dialog.confirm();
1458        assert!(dialog.is_confirmed());
1459        assert!(!dialog.is_pending());
1460    }
1461
1462    #[test]
1463    fn test_dialog_widget_cancel() {
1464        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1465        dialog.cancel();
1466        assert!(dialog.is_cancelled());
1467        assert!(!dialog.is_pending());
1468    }
1469
1470    #[test]
1471    fn test_dialog_widget_validation() {
1472        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message")
1473            .with_validator(|input| !input.is_empty() && input.len() >= 3);
1474
1475        dialog.insert_char('a');
1476        dialog.insert_char('b');
1477        assert!(!dialog.validate());
1478
1479        dialog.insert_char('c');
1480        assert!(dialog.validate());
1481    }
1482
1483    #[test]
1484    fn test_dialog_widget_confirm_dialog() {
1485        let mut dialog = DialogWidget::new(DialogType::Confirm, "Confirm", "Are you sure?");
1486        assert!(dialog.confirmed.is_none());
1487
1488        dialog.confirm();
1489        assert_eq!(dialog.confirmed, Some(true));
1490        assert!(dialog.is_confirmed());
1491
1492        let mut dialog = DialogWidget::new(DialogType::Confirm, "Confirm", "Are you sure?");
1493        dialog.cancel();
1494        assert_eq!(dialog.confirmed, Some(false));
1495        assert!(dialog.is_cancelled());
1496    }
1497
1498    #[test]
1499    fn test_dialog_widget_clear_input() {
1500        let mut dialog = DialogWidget::new(DialogType::Input, "Title", "Message");
1501        dialog.insert_char('t');
1502        dialog.insert_char('e');
1503        dialog.insert_char('s');
1504        dialog.insert_char('t');
1505
1506        dialog.clear_input();
1507        assert!(dialog.get_input().is_empty());
1508        assert_eq!(dialog.cursor, 0);
1509    }
1510
1511    #[test]
1512    fn test_split_view_widget() {
1513        let mut split = SplitViewWidget::new();
1514        split.set_left("left content");
1515        split.set_right("right content");
1516
1517        assert_eq!(split.left_content, "left content");
1518        assert_eq!(split.right_content, "right content");
1519        assert_eq!(split.split_ratio, 50);
1520    }
1521
1522    #[test]
1523    fn test_split_view_adjust() {
1524        let mut split = SplitViewWidget::new();
1525        split.adjust_split(10);
1526        assert_eq!(split.split_ratio, 60);
1527
1528        split.adjust_split(-20);
1529        assert_eq!(split.split_ratio, 40);
1530    }
1531
1532    #[test]
1533    fn test_split_view_direction() {
1534        let split = SplitViewWidget::new();
1535        assert_eq!(split.direction, SplitDirection::Vertical);
1536
1537        let split = SplitViewWidget::horizontal();
1538        assert_eq!(split.direction, SplitDirection::Horizontal);
1539    }
1540
1541    #[test]
1542    fn test_split_view_panel_switching() {
1543        let mut split = SplitViewWidget::new();
1544        split.set_left("left");
1545        split.set_right("right");
1546
1547        assert_eq!(split.active_panel, 0);
1548        assert_eq!(split.active_content(), "left");
1549
1550        split.switch_panel();
1551        assert_eq!(split.active_panel, 1);
1552        assert_eq!(split.active_content(), "right");
1553
1554        split.switch_panel();
1555        assert_eq!(split.active_panel, 0);
1556    }
1557
1558    #[test]
1559    fn test_split_view_scrolling() {
1560        let mut split = SplitViewWidget::new();
1561        split.set_left("left content");
1562        split.set_right("right content");
1563
1564        assert_eq!(split.left_scroll, 0);
1565        split.scroll_down();
1566        assert_eq!(split.left_scroll, 1);
1567
1568        split.scroll_up();
1569        assert_eq!(split.left_scroll, 0);
1570
1571        split.switch_panel();
1572        split.scroll_down();
1573        assert_eq!(split.right_scroll, 1);
1574    }
1575
1576    #[test]
1577    fn test_tab_widget() {
1578        let mut tabs = TabWidget::new();
1579        tabs.add_tab("Tab 1");
1580        tabs.add_tab("Tab 2");
1581        tabs.add_tab("Tab 3");
1582
1583        assert_eq!(tabs.active, 0);
1584        tabs.select_next();
1585        assert_eq!(tabs.active, 1);
1586        tabs.select_prev();
1587        assert_eq!(tabs.active, 0);
1588    }
1589
1590    #[test]
1591    fn test_tab_widget_with_content() {
1592        let mut tabs = TabWidget::new();
1593        tabs.add_tab_with_content("Tab 1", "Content 1");
1594        tabs.add_tab_with_content("Tab 2", "Content 2");
1595
1596        assert_eq!(tabs.active_content(), Some(&"Content 1".to_string()));
1597
1598        tabs.select_next();
1599        assert_eq!(tabs.active_content(), Some(&"Content 2".to_string()));
1600    }
1601
1602    #[test]
1603    fn test_tab_widget_select_by_index() {
1604        let mut tabs = TabWidget::new();
1605        tabs.add_tab("Tab 1");
1606        tabs.add_tab("Tab 2");
1607        tabs.add_tab("Tab 3");
1608
1609        tabs.select_tab(2);
1610        assert_eq!(tabs.active, 2);
1611
1612        tabs.select_tab(0);
1613        assert_eq!(tabs.active, 0);
1614    }
1615
1616    #[test]
1617    fn test_tab_widget_close_tab() {
1618        let mut tabs = TabWidget::new();
1619        tabs.add_tab("Tab 1");
1620        tabs.add_tab("Tab 2");
1621        tabs.add_tab("Tab 3");
1622
1623        assert_eq!(tabs.tab_count(), 3);
1624
1625        tabs.close_tab(1);
1626        assert_eq!(tabs.tab_count(), 2);
1627        assert_eq!(tabs.active_tab(), Some(&"Tab 1".to_string()));
1628    }
1629
1630    #[test]
1631    fn test_tab_widget_close_active_tab() {
1632        let mut tabs = TabWidget::new();
1633        tabs.add_tab("Tab 1");
1634        tabs.add_tab("Tab 2");
1635        tabs.add_tab("Tab 3");
1636
1637        // Select last tab
1638        tabs.select_tab(2);
1639        assert_eq!(tabs.active, 2);
1640        tabs.close_active_tab();
1641        assert_eq!(tabs.tab_count(), 2);
1642        // After closing tab at index 2 (last), active should be adjusted to 1
1643        assert_eq!(tabs.active, 1);
1644    }
1645
1646    #[test]
1647    fn test_tab_widget_set_content() {
1648        let mut tabs = TabWidget::new();
1649        tabs.add_tab("Tab 1");
1650        tabs.set_active_content("New content");
1651
1652        assert_eq!(tabs.active_content(), Some(&"New content".to_string()));
1653    }
1654
1655    #[test]
1656    fn test_tab_widget_visible_tabs() {
1657        let mut tabs = TabWidget::new();
1658        for i in 0..10 {
1659            tabs.add_tab(format!("Tab {}", i));
1660        }
1661
1662        let visible = tabs.visible_tabs(5);
1663        assert_eq!(visible.len(), 5);
1664
1665        tabs.scroll = 3;
1666        let visible = tabs.visible_tabs(5);
1667        assert_eq!(visible.len(), 5);
1668    }
1669
1670    #[test]
1671    fn test_tab_widget_clear() {
1672        let mut tabs = TabWidget::new();
1673        tabs.add_tab("Tab 1");
1674        tabs.add_tab("Tab 2");
1675
1676        tabs.clear();
1677        assert!(tabs.is_empty());
1678        assert_eq!(tabs.active, 0);
1679    }
1680
1681    #[test]
1682    fn test_mode_indicator_creation() {
1683        let indicator = ModeIndicator::new(AppMode::Chat);
1684        assert_eq!(indicator.mode, AppMode::Chat);
1685        assert!(indicator.show_shortcut);
1686    }
1687
1688    #[test]
1689    fn test_mode_indicator_display_text() {
1690        let indicator = ModeIndicator::new(AppMode::Chat);
1691        let text = indicator.display_text();
1692        assert!(text.contains("Chat"));
1693        assert!(text.contains("Ctrl+1"));
1694    }
1695
1696    #[test]
1697    fn test_mode_indicator_short_text() {
1698        let indicator = ModeIndicator::new(AppMode::Command);
1699        assert_eq!(indicator.short_text(), "Command");
1700    }
1701
1702    #[test]
1703    fn test_mode_indicator_set_mode() {
1704        let mut indicator = ModeIndicator::new(AppMode::Chat);
1705        indicator.set_mode(AppMode::Diff);
1706        assert_eq!(indicator.mode, AppMode::Diff);
1707    }
1708
1709    #[test]
1710    fn test_mode_indicator_toggle_shortcut() {
1711        let mut indicator = ModeIndicator::new(AppMode::Chat);
1712        assert!(indicator.show_shortcut);
1713        indicator.toggle_shortcut_display();
1714        assert!(!indicator.show_shortcut);
1715        indicator.toggle_shortcut_display();
1716        assert!(indicator.show_shortcut);
1717    }
1718
1719    #[test]
1720    fn test_mode_indicator_get_capabilities() {
1721        let indicator = ModeIndicator::new(AppMode::Chat);
1722        let caps = indicator.get_capabilities();
1723        assert!(caps.contains(&"QuestionAnswering"));
1724        assert!(caps.contains(&"FreeformChat"));
1725    }
1726
1727    #[test]
1728    fn test_mode_indicator_capabilities_text() {
1729        let indicator = ModeIndicator::new(AppMode::Command);
1730        let text = indicator.capabilities_text();
1731        assert!(text.contains("Capabilities:"));
1732        assert!(text.contains("CodeGeneration"));
1733    }
1734
1735    #[test]
1736    fn test_mode_indicator_toggle_capabilities() {
1737        let mut indicator = ModeIndicator::new(AppMode::Chat);
1738        assert!(!indicator.show_capabilities);
1739        indicator.toggle_capabilities_display();
1740        assert!(indicator.show_capabilities);
1741        indicator.toggle_capabilities_display();
1742        assert!(!indicator.show_capabilities);
1743    }
1744
1745    #[test]
1746    fn test_mode_indicator_show_hide_capabilities() {
1747        let mut indicator = ModeIndicator::new(AppMode::Chat);
1748        assert!(!indicator.show_capabilities);
1749
1750        indicator.show_capabilities_enabled();
1751        assert!(indicator.show_capabilities);
1752
1753        indicator.hide_capabilities_enabled();
1754        assert!(!indicator.show_capabilities);
1755    }
1756
1757    #[test]
1758    fn test_mode_selection_menu_creation() {
1759        let menu = ModeSelectionMenu::new();
1760        assert!(!menu.open);
1761        assert_eq!(menu.modes.len(), 4);
1762        assert_eq!(menu.selected_mode(), AppMode::Chat);
1763    }
1764
1765    #[test]
1766    fn test_mode_selection_menu_open_close() {
1767        let mut menu = ModeSelectionMenu::new();
1768        assert!(!menu.open);
1769
1770        menu.open(AppMode::Chat);
1771        assert!(menu.open);
1772
1773        menu.close();
1774        assert!(!menu.open);
1775    }
1776
1777    #[test]
1778    fn test_mode_selection_menu_navigation() {
1779        let mut menu = ModeSelectionMenu::new();
1780        menu.open(AppMode::Chat);
1781
1782        assert_eq!(menu.selected_mode(), AppMode::Chat);
1783        menu.select_next();
1784        assert_eq!(menu.selected_mode(), AppMode::Command);
1785        menu.select_next();
1786        assert_eq!(menu.selected_mode(), AppMode::Diff);
1787        menu.select_prev();
1788        assert_eq!(menu.selected_mode(), AppMode::Command);
1789    }
1790
1791    #[test]
1792    fn test_mode_selection_menu_wrap_around() {
1793        let mut menu = ModeSelectionMenu::new();
1794        menu.open(AppMode::Help);
1795
1796        assert_eq!(menu.selected_mode(), AppMode::Help);
1797        menu.select_next();
1798        assert_eq!(menu.selected_mode(), AppMode::Chat);
1799
1800        menu.select_prev();
1801        assert_eq!(menu.selected_mode(), AppMode::Help);
1802    }
1803
1804    #[test]
1805    fn test_mode_selection_menu_confirm() {
1806        let mut menu = ModeSelectionMenu::new();
1807        menu.open(AppMode::Chat);
1808        menu.select_next();
1809
1810        let selected = menu.confirm_switch();
1811        assert_eq!(selected, AppMode::Command);
1812        assert!(!menu.open);
1813    }
1814
1815    #[test]
1816    fn test_mode_selection_menu_descriptions() {
1817        let menu = ModeSelectionMenu::new();
1818        let descriptions = menu.get_mode_descriptions();
1819        assert_eq!(descriptions.len(), 4);
1820        assert!(descriptions[0].1.contains("Chat"));
1821    }
1822
1823    #[test]
1824    fn test_mode_selection_menu_shortcuts() {
1825        let menu = ModeSelectionMenu::new();
1826        let shortcuts = menu.get_shortcuts();
1827        assert_eq!(shortcuts.len(), 4);
1828        assert_eq!(shortcuts[0].0, "Ctrl+1");
1829    }
1830
1831    #[test]
1832    fn test_vim_keybindings_creation() {
1833        let vim = VimKeybindings::new();
1834        assert!(!vim.enabled);
1835        assert_eq!(vim.mode, VimMode::Normal);
1836        assert!(vim.command_buffer.is_empty());
1837    }
1838
1839    #[test]
1840    fn test_vim_keybindings_enable_disable() {
1841        let mut vim = VimKeybindings::new();
1842        assert!(!vim.enabled);
1843
1844        vim.enable();
1845        assert!(vim.enabled);
1846        assert_eq!(vim.mode, VimMode::Normal);
1847
1848        vim.disable();
1849        assert!(!vim.enabled);
1850    }
1851
1852    #[test]
1853    fn test_vim_keybindings_toggle() {
1854        let mut vim = VimKeybindings::new();
1855        assert!(!vim.enabled);
1856
1857        vim.toggle();
1858        assert!(vim.enabled);
1859
1860        vim.toggle();
1861        assert!(!vim.enabled);
1862    }
1863
1864    #[test]
1865    fn test_vim_keybindings_mode_switching() {
1866        let mut vim = VimKeybindings::new();
1867        vim.enable();
1868
1869        assert_eq!(vim.mode, VimMode::Normal);
1870
1871        vim.enter_insert();
1872        assert_eq!(vim.mode, VimMode::Insert);
1873
1874        vim.enter_visual();
1875        assert_eq!(vim.mode, VimMode::Visual);
1876
1877        vim.enter_command();
1878        assert_eq!(vim.mode, VimMode::Command);
1879
1880        vim.enter_normal();
1881        assert_eq!(vim.mode, VimMode::Normal);
1882    }
1883
1884    #[test]
1885    fn test_vim_keybindings_command_buffer() {
1886        let mut vim = VimKeybindings::new();
1887        vim.enable();
1888        vim.enter_command();
1889
1890        vim.add_to_command('w');
1891        vim.add_to_command('q');
1892
1893        assert_eq!(vim.get_command(), "wq");
1894
1895        vim.clear_command();
1896        assert!(vim.get_command().is_empty());
1897    }
1898
1899    #[test]
1900    fn test_vim_keybindings_mode_checks() {
1901        let mut vim = VimKeybindings::new();
1902        vim.enable();
1903
1904        assert!(vim.is_normal());
1905        assert!(!vim.is_insert());
1906        assert!(!vim.is_visual());
1907        assert!(!vim.is_command());
1908
1909        vim.enter_insert();
1910        assert!(!vim.is_normal());
1911        assert!(vim.is_insert());
1912
1913        vim.enter_visual();
1914        assert!(vim.is_visual());
1915
1916        vim.enter_command();
1917        assert!(vim.is_command());
1918    }
1919
1920    #[test]
1921    fn test_vim_keybindings_disabled_mode_checks() {
1922        let vim = VimKeybindings::new();
1923        assert!(!vim.is_normal());
1924        assert!(!vim.is_insert());
1925        assert!(!vim.is_visual());
1926        assert!(!vim.is_command());
1927    }
1928
1929    #[test]
1930    fn test_vim_keybindings_enter_normal_clears_command() {
1931        let mut vim = VimKeybindings::new();
1932        vim.enable();
1933        vim.enter_command();
1934        vim.add_to_command('w');
1935        vim.add_to_command('q');
1936
1937        assert!(!vim.get_command().is_empty());
1938
1939        vim.enter_normal();
1940        assert!(vim.get_command().is_empty());
1941    }
1942
1943    #[test]
1944    fn test_vim_keybindings_disable_resets_mode() {
1945        let mut vim = VimKeybindings::new();
1946        vim.enable();
1947        vim.enter_command();
1948        vim.add_to_command('t');
1949        vim.add_to_command('e');
1950        vim.add_to_command('s');
1951        vim.add_to_command('t');
1952
1953        vim.disable();
1954        assert_eq!(vim.mode, VimMode::Normal);
1955        assert!(vim.get_command().is_empty());
1956    }
1957}