vtcode_tui/core_tui/app/session/
palette.rs1use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
9use std::path::PathBuf;
10
11use super::{
12 AppSession,
13 agent_palette::{AgentEntry, AgentPalette, extract_agent_reference},
14 file_palette::{FilePalette, extract_file_reference},
15};
16use crate::core_tui::app::session::slash;
17use crate::core_tui::app::session::transient::TransientSurface;
18use crate::core_tui::app::types::AgentPaletteItem;
19
20impl AppSession {
21 pub(super) fn load_agent_palette(&mut self, agents: Vec<AgentPaletteItem>) {
22 let mut palette = AgentPalette::new();
23 palette.load_agents(
24 agents
25 .into_iter()
26 .map(|agent| AgentEntry {
27 display_name: format!("@agent-{}", agent.name),
28 name: agent.name,
29 description: agent.description,
30 })
31 .collect(),
32 );
33 self.agent_palette = Some(palette);
34 self.agent_palette_active = false;
35 self.check_agent_reference_trigger();
36 }
37
38 pub fn check_agent_reference_trigger(&mut self) {
39 let cursor = self.core.input_manager.cursor();
40 let content = self.core.input_manager.content();
41 let trigger = extract_agent_reference(content, cursor);
42
43 if let Some(palette) = self.agent_palette.as_mut()
44 && let Some((_start, _end, query)) = trigger
45 {
46 palette.set_filter(query);
47 if !self.agent_palette_active {
48 self.ensure_inline_lists_visible_for_trigger();
49 self.agent_palette_active = true;
50 self.show_transient_surface(TransientSurface::AgentPalette);
51 self.mark_dirty();
52 }
53 return;
54 }
55
56 if self.agent_palette_active {
57 self.close_agent_palette();
58 }
59 }
60
61 pub(super) fn close_agent_palette(&mut self) {
62 self.agent_palette_active = false;
63 self.close_transient_surface(TransientSurface::AgentPalette);
64
65 if let Some(palette) = self.agent_palette.as_mut() {
66 palette.set_filter(String::new());
67 }
68 }
69
70 pub(super) fn handle_agent_palette_key(&mut self, key: &KeyEvent) -> bool {
71 if !self.agent_palette_visible() {
72 return false;
73 }
74
75 let Some(palette) = self.agent_palette.as_mut() else {
76 return false;
77 };
78
79 match key.code {
80 KeyCode::Up => {
81 palette.move_selection_up();
82 self.mark_dirty();
83 true
84 }
85 KeyCode::Down => {
86 palette.move_selection_down();
87 self.mark_dirty();
88 true
89 }
90 KeyCode::Tab => {
91 palette.select_best_match();
92 self.mark_dirty();
93 true
94 }
95 KeyCode::Enter => {
96 let selected_name = palette.get_selected().map(|entry| entry.name.clone());
97 if let Some(name) = selected_name {
98 self.insert_agent_reference(&name);
99 self.close_agent_palette();
100 self.mark_dirty();
101 true
102 } else {
103 self.close_agent_palette();
104 self.mark_dirty();
105 false
106 }
107 }
108 KeyCode::Esc => {
109 self.close_agent_palette();
110 self.mark_dirty();
111 true
112 }
113 KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
114 palette.move_selection_down();
115 self.mark_dirty();
116 true
117 }
118 KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
119 palette.move_selection_up();
120 self.mark_dirty();
121 true
122 }
123 _ => false,
124 }
125 }
126
127 pub(crate) fn insert_agent_reference(&mut self, agent_name: &str) {
128 if let Some((start, end, _)) = extract_agent_reference(
129 self.core.input_manager.content(),
130 self.core.input_manager.cursor(),
131 ) {
132 let before = &self.core.input_manager.content()[..start];
133 let after = &self.core.input_manager.content()[end..];
134 let reference_alias = format!("@agent-{}", agent_name);
135 let new_content = format!("{}{} {}", before, reference_alias, after);
136 let new_cursor = start + reference_alias.len() + 1;
137
138 self.core.input_manager.set_content(new_content);
139 self.core.input_manager.set_cursor(new_cursor);
140 slash::update_slash_suggestions(self);
141 }
142 }
143
144 pub(super) fn load_file_palette(&mut self, files: Vec<String>, workspace: PathBuf) {
146 let mut palette = FilePalette::new(workspace);
147 palette.load_files(files);
148 self.file_palette = Some(palette);
149 self.file_palette_active = false;
150 self.check_file_reference_trigger();
151 }
152
153 pub fn check_file_reference_trigger(&mut self) {
155 if self.agent_palette_visible() {
156 if self.file_palette_active {
157 self.close_file_palette();
158 }
159 return;
160 }
161
162 if let Some(palette) = self.file_palette.as_mut() {
163 if let Some((_start, _end, query)) = extract_file_reference(
164 self.core.input_manager.content(),
165 self.core.input_manager.cursor(),
166 ) {
167 palette.set_filter(query);
168 if !self.file_palette_active {
169 self.ensure_inline_lists_visible_for_trigger();
170 self.file_palette_active = true;
171 self.show_transient_surface(TransientSurface::FilePalette);
172 self.mark_dirty();
173 }
174 } else if self.file_palette_active {
175 self.close_file_palette();
176 }
177 }
178 }
179
180 pub(super) fn close_file_palette(&mut self) {
182 self.file_palette_active = false;
183 self.close_transient_surface(TransientSurface::FilePalette);
184
185 if let Some(palette) = self.file_palette.as_mut() {
187 palette.set_filter(String::new());
188 }
189 }
190
191 pub(super) fn handle_file_palette_key(&mut self, key: &KeyEvent) -> bool {
195 if !self.file_palette_visible() {
196 return false;
197 }
198
199 let Some(palette) = self.file_palette.as_mut() else {
200 return false;
201 };
202
203 match key.code {
204 KeyCode::Up => {
205 palette.move_selection_up();
206 self.mark_dirty();
207 true
208 }
209 KeyCode::Down => {
210 palette.move_selection_down();
211 self.mark_dirty();
212 true
213 }
214 KeyCode::Left => {
215 palette.collapse_selected();
216 self.mark_dirty();
217 true
218 }
219 KeyCode::Right => {
220 palette.expand_selected();
221 self.mark_dirty();
222 true
223 }
224 KeyCode::Tab => {
225 palette.select_best_match();
226 self.mark_dirty();
227 true
228 }
229 KeyCode::Enter => {
230 if palette.selected_is_expandable_group() {
231 palette.toggle_selected();
232 self.mark_dirty();
233 true
234 } else {
235 let selected_path = palette.get_selected().map(|e| e.relative_path.clone());
236 if let Some(path) = selected_path {
237 self.insert_file_reference(&path);
238 self.close_file_palette();
239 self.mark_dirty();
240 true
241 } else {
242 self.close_file_palette();
243 self.mark_dirty();
244 false
245 }
246 }
247 }
248 KeyCode::Esc => {
249 self.close_file_palette();
250 self.mark_dirty();
251 true
252 }
253 KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
254 palette.move_selection_down();
255 self.mark_dirty();
256 true
257 }
258 KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
259 palette.move_selection_up();
260 self.mark_dirty();
261 true
262 }
263 _ => false,
264 }
265 }
266
267 pub(crate) fn insert_file_reference(&mut self, file_path: &str) {
269 if let Some((start, end, _)) = extract_file_reference(
270 self.core.input_manager.content(),
271 self.core.input_manager.cursor(),
272 ) {
273 let before = &self.core.input_manager.content()[..start];
274 let after = &self.core.input_manager.content()[end..];
275 let reference_alias = format!("@{}", file_path);
276 let new_content = format!("{}{} {}", before, reference_alias, after);
277 let new_cursor = start + reference_alias.len() + 1;
278
279 self.core.input_manager.set_content(new_content);
280 self.core.input_manager.set_cursor(new_cursor);
281 slash::update_slash_suggestions(self);
282 }
283 }
284}