Skip to main content

vtcode_tui/core_tui/app/session/
palette.rs

1/// Palette management operations for Session
2///
3/// This module handles file palette interactions including:
4/// - Loading and closing palettes
5/// - Checking and handling triggers
6/// - Key event handling for palette navigation
7/// - Reference insertion
8use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
9use std::path::PathBuf;
10
11use super::{
12    AppSession,
13    file_palette::{FilePalette, extract_file_reference},
14};
15use crate::core_tui::app::session::slash;
16use crate::core_tui::app::session::transient::TransientSurface;
17
18impl AppSession {
19    /// Load the file palette with files from the workspace
20    pub(super) fn load_file_palette(&mut self, files: Vec<String>, workspace: PathBuf) {
21        let mut palette = FilePalette::new(workspace);
22        palette.load_files(files);
23        self.file_palette = Some(palette);
24        self.file_palette_active = false;
25        self.check_file_reference_trigger();
26    }
27
28    /// Check if the current input should trigger the file palette
29    pub fn check_file_reference_trigger(&mut self) {
30        if let Some(palette) = self.file_palette.as_mut() {
31            if let Some((_start, _end, query)) = extract_file_reference(
32                self.core.input_manager.content(),
33                self.core.input_manager.cursor(),
34            ) {
35                palette.set_filter(query);
36                if !self.file_palette_active {
37                    self.ensure_inline_lists_visible_for_trigger();
38                    self.file_palette_active = true;
39                    self.show_transient_surface(TransientSurface::FilePalette);
40                    self.mark_dirty();
41                }
42            } else if self.file_palette_active {
43                self.close_file_palette();
44            }
45        }
46    }
47
48    /// Close the file palette and clean up resources
49    pub(super) fn close_file_palette(&mut self) {
50        self.file_palette_active = false;
51        self.close_transient_surface(TransientSurface::FilePalette);
52
53        // Clean up resources when closing to free memory
54        if let Some(palette) = self.file_palette.as_mut() {
55            palette.set_filter(String::new());
56        }
57    }
58
59    /// Handle key events for the file palette
60    ///
61    /// Returns true if the key was handled by the palette
62    pub(super) fn handle_file_palette_key(&mut self, key: &KeyEvent) -> bool {
63        if !self.file_palette_visible() {
64            return false;
65        }
66
67        let Some(palette) = self.file_palette.as_mut() else {
68            return false;
69        };
70
71        match key.code {
72            KeyCode::Up => {
73                palette.move_selection_up();
74                self.mark_dirty();
75                true
76            }
77            KeyCode::Down => {
78                palette.move_selection_down();
79                self.mark_dirty();
80                true
81            }
82            KeyCode::Tab => {
83                palette.select_best_match();
84                self.mark_dirty();
85                true
86            }
87            KeyCode::Enter => {
88                let selected_path = palette.get_selected().map(|e| e.relative_path.clone());
89                if let Some(path) = selected_path {
90                    self.insert_file_reference(&path);
91                    self.close_file_palette();
92                    self.mark_dirty();
93                    true // Selection made: consume event
94                } else {
95                    // No selection: close palette and fall through to normal submit
96                    self.close_file_palette();
97                    self.mark_dirty();
98                    false // Let normal Enter handling proceed
99                }
100            }
101            KeyCode::Esc => {
102                self.close_file_palette();
103                self.mark_dirty();
104                true
105            }
106            KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
107                palette.move_selection_down();
108                self.mark_dirty();
109                true
110            }
111            KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
112                palette.move_selection_up();
113                self.mark_dirty();
114                true
115            }
116            _ => false,
117        }
118    }
119
120    /// Insert a file reference into the input at the current position
121    pub(crate) fn insert_file_reference(&mut self, file_path: &str) {
122        if let Some((start, end, _)) = extract_file_reference(
123            self.core.input_manager.content(),
124            self.core.input_manager.cursor(),
125        ) {
126            let before = &self.core.input_manager.content()[..start];
127            let after = &self.core.input_manager.content()[end..];
128            let reference_alias = format!("@{}", file_path);
129            let new_content = format!("{}{} {}", before, reference_alias, after);
130            let new_cursor = start + reference_alias.len() + 1;
131
132            self.core.input_manager.set_content(new_content);
133            self.core.input_manager.set_cursor(new_cursor);
134            slash::update_slash_suggestions(self);
135        }
136    }
137}