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