intelli_shell/component/mod.rs
1use async_trait::async_trait;
2use color_eyre::Result;
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
4use ratatui::{Frame, layout::Rect};
5
6use crate::{
7 app::Action,
8 config::{KeyBindingAction, KeyBindingsConfig},
9 process::ProcessOutput,
10};
11
12pub mod completion_edit;
13pub mod completion_list;
14pub mod edit;
15pub mod pick;
16pub mod search;
17pub mod variable;
18
19/// Defines the behavior for a UI component within the application.
20///
21/// Components are responsible for rendering themselves, handling user input, and managing their internal state. They
22/// can also perform logic updates periodically via the `tick` method.
23#[async_trait]
24pub trait Component: Send {
25 /// Retrieves the component name, for debugging purposes
26 fn name(&self) -> &'static str;
27
28 /// Calculates the minimum height required by this component to be rendered correctly when inline (in rows)
29 fn min_inline_height(&self) -> u16;
30
31 /// Allows the component to initialize any internal state or resources it needs before being used as well as peeks
32 /// into the component before rendering, for examplle to give a straight result, switch component or continue
33 /// with the TUI.
34 ///
35 /// It can be called multiple times, for example if a component is re-used after being switched out.
36 async fn init_and_peek(&mut self) -> Result<Action> {
37 Ok(Action::NoOp)
38 }
39
40 /// Processes time-based logic, internal state updates, or background tasks for the component.
41 ///
42 /// This method is called periodically by the application's main loop and is not directly tied to rendering or user
43 /// input events.
44 /// It can be used for animations, polling asynchronous operations, or updating internal timers.
45 fn tick(&mut self) -> Result<Action> {
46 Ok(Action::NoOp)
47 }
48
49 /// Renders the component's UI within the given `area` of the `frame`
50 fn render(&mut self, frame: &mut Frame, area: Rect);
51
52 /// Finalizes the component's current operation and returns its output with the current state.
53 ///
54 /// This method is typically called when the user signals that they want to exit the command.
55 fn exit(&mut self) -> Result<Action> {
56 Ok(Action::Quit(ProcessOutput::success()))
57 }
58
59 /// Processes a paste event, typically from clipboard paste into the terminal.
60 ///
61 /// This method is called when the application detects a paste action. The `content` parameter contains the string
62 /// of text that was pasted.
63 ///
64 /// The default implementation will just call [`insert_text`](Component::insert_text).
65 fn process_paste_event(&mut self, content: String) -> Result<Action> {
66 self.insert_text(content)
67 }
68
69 /// Processes a key press event.
70 ///
71 /// This method is the primary handler for keyboard input. It receives a `KeyEvent` and is responsible for
72 /// translating it into component-specific behaviors or application-level [`Action`]s by consulting the provided
73 /// `keybindings` or using hardcoded defaults.
74 ///
75 /// Implementors can override this method to provide entirely custom key handling, optionally calling
76 /// [`default_process_key_event`](Component::default_process_key_event) first and checking its result.
77 async fn process_key_event(&mut self, keybindings: &KeyBindingsConfig, key: KeyEvent) -> Result<Action> {
78 Ok(self
79 .default_process_key_event(keybindings, key)
80 .await?
81 .unwrap_or_default())
82 }
83
84 /// The default behavior for [`process_key_event`](Component::process_key_event) with a baseline set of key mappings
85 /// by matching against the event. It calls other granular methods of this trait (e.g.,
86 /// [`move_up`](Component::move_up), [`insert_char`](Component::insert_char)).
87 ///
88 /// - If this default method returns `Ok(Some(action))`, it means the key was recognized and mapped to an `Action`.
89 /// - If it returns `Ok(None)`, it means the specific key event was **not handled** by this default logic, allowing
90 /// an overriding implementation to then process it.
91 async fn default_process_key_event(
92 &mut self,
93 keybindings: &KeyBindingsConfig,
94 key: KeyEvent,
95 ) -> Result<Option<Action>> {
96 // Check customizable key bindings first
97 if let Some(action) = keybindings.get_action_matching(&key) {
98 return Ok(Some(match action {
99 KeyBindingAction::Quit => self.exit()?,
100 KeyBindingAction::Update => self.selection_update().await?,
101 KeyBindingAction::Delete => self.selection_delete().await?,
102 KeyBindingAction::Confirm => self.selection_confirm().await?,
103 KeyBindingAction::Execute => self.selection_execute().await?,
104 KeyBindingAction::AI => self.prompt_ai().await?,
105 KeyBindingAction::SearchMode => self.toggle_search_mode()?,
106 KeyBindingAction::SearchUserOnly => self.toggle_search_user_only()?,
107 }));
108 }
109
110 // If no configured binding matched, fall back to default handling
111 Ok(match key.code {
112 #[cfg(debug_assertions)]
113 // Debug
114 KeyCode::Char('p') if key.modifiers == KeyModifiers::ALT => panic!("Debug panic!"),
115 // Selection / Movement
116 KeyCode::Char('k') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_prev()?),
117 KeyCode::Char('j') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_next()?),
118 KeyCode::Home => Some(self.move_home(key.modifiers == KeyModifiers::CONTROL)?),
119 KeyCode::Char('a') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_home(false)?),
120 KeyCode::End => Some(self.move_end(key.modifiers == KeyModifiers::CONTROL)?),
121 KeyCode::Char('e') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_end(false)?),
122 KeyCode::Up => Some(self.move_up()?),
123 KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_up()?),
124 KeyCode::Down => Some(self.move_down()?),
125 KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_down()?),
126 KeyCode::Right => Some(self.move_right(key.modifiers == KeyModifiers::CONTROL)?),
127 KeyCode::Char('f') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_right(false)?),
128 KeyCode::Char('f') if key.modifiers == KeyModifiers::ALT => Some(self.move_right(true)?),
129 KeyCode::Left => Some(self.move_left(key.modifiers == KeyModifiers::CONTROL)?),
130 KeyCode::Char('b') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_left(false)?),
131 KeyCode::Char('b') if key.modifiers == KeyModifiers::ALT => Some(self.move_left(true)?),
132 // Undo / redo
133 KeyCode::Char('z') if key.modifiers == KeyModifiers::CONTROL => Some(self.undo()?),
134 KeyCode::Char('u') if key.modifiers == KeyModifiers::CONTROL => Some(self.undo()?),
135 KeyCode::Char('y') if key.modifiers == KeyModifiers::CONTROL => Some(self.redo()?),
136 KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => Some(self.redo()?),
137 // Text edit
138 KeyCode::Backspace => Some(self.delete(true, key.modifiers == KeyModifiers::CONTROL)?),
139 KeyCode::Char('h') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(true, false)?),
140 KeyCode::Char('w') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(true, true)?),
141 KeyCode::Delete => Some(self.delete(false, key.modifiers == KeyModifiers::CONTROL)?),
142 KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(false, false)?),
143 KeyCode::Char('d') if key.modifiers == KeyModifiers::ALT => Some(self.delete(false, true)?),
144 KeyCode::Enter if key.modifiers == KeyModifiers::SHIFT => Some(self.insert_newline()?),
145 KeyCode::Enter if key.modifiers == KeyModifiers::ALT => Some(self.insert_newline()?),
146 KeyCode::Char('m') if key.modifiers == KeyModifiers::CONTROL => Some(self.insert_newline()?),
147 KeyCode::Char(c) => Some(self.insert_char(c)?),
148 // Don't process other events
149 _ => None,
150 })
151 }
152
153 /// Processes a mouse event.
154 ///
155 /// This method is called when a mouse action (like click, scroll, move) occurs within the terminal, provided mouse
156 /// capture is enabled.
157 ///
158 /// Returns [Some] if the event was processed or [None] if not (the default).
159 fn process_mouse_event(&mut self, mouse: MouseEvent) -> Result<Action> {
160 let _ = mouse;
161 Ok(Action::NoOp)
162 }
163
164 /// Called when the component gains focus within the application.
165 ///
166 /// Components can implement this method to change their appearance (e.g., show a highlight border), enable input
167 /// handling, initialize internal state specific to being active, or perform other setup tasks.
168 fn focus_gained(&mut self) -> Result<Action> {
169 Ok(Action::NoOp)
170 }
171
172 /// Called when the component loses focus within the application.
173 ///
174 /// Components can implement this method to change their appearance (e.g., remove a highlight border), disable input
175 /// handling, persist any temporary state, or perform other cleanup tasks.
176 fn focus_lost(&mut self) -> Result<Action> {
177 Ok(Action::NoOp)
178 }
179
180 /// Handles a terminal resize event, informing the component of the new global terminal dimensions.
181 ///
182 /// This method is called when the overall terminal window size changes.
183 /// Components can use this notification to adapt internal state, pre-calculate layout-dependent values, or
184 /// invalidate caches before a subsequent `render` call, which will likely provide a new drawing area (`Rect`)
185 /// based on these new terminal dimensions.
186 ///
187 /// **Note:** The `width` and `height` parameters typically represent the total new dimensions of the terminal in
188 /// columns and rows, not necessarily the area allocated to this specific component.
189 fn resize(&mut self, width: u16, height: u16) -> Result<Action> {
190 _ = (width, height);
191 Ok(Action::NoOp)
192 }
193
194 /// Handles a request to move the selection or focus upwards within the component.
195 ///
196 /// The exact behavior depends on the component's nature (e.g., moving up in a list, focusing an element above the
197 /// current one).
198 fn move_up(&mut self) -> Result<Action> {
199 Ok(Action::NoOp)
200 }
201
202 /// Handles a request to move the selection or focus downwards within the component.
203 ///
204 /// The exact behavior depends on the component's nature (e.g., moving down in a list, focusing an element below the
205 /// current one).
206 fn move_down(&mut self) -> Result<Action> {
207 Ok(Action::NoOp)
208 }
209
210 /// Handles a request to move the selection or focus to the left within the component.
211 ///
212 /// The `word` parameter indicates whether the movement should be applied to a whole word or just a single
213 /// character.
214 ///
215 /// The exact behavior depends on the component's nature (e.g., moving left in a text input, focusing an element to
216 /// the left of the current one).
217 fn move_left(&mut self, word: bool) -> Result<Action> {
218 let _ = word;
219 Ok(Action::NoOp)
220 }
221
222 /// Handles a request to move the selection or focus to the right within the component.
223 ///
224 /// The `word` parameter indicates whether the movement should be applied to a whole word or just a single
225 /// character.
226 ///
227 /// The exact behavior depends on the component's nature (e.g., moving right in a text input, focusing an element to
228 /// the right of the current one).
229 fn move_right(&mut self, word: bool) -> Result<Action> {
230 let _ = word;
231 Ok(Action::NoOp)
232 }
233
234 /// Handles a request to move the selection to the previous logical item or element.
235 ///
236 /// This is often used for navigating backwards in a sequence (e.g., previous tab,
237 /// previous item in a wizard) that may not map directly to simple directional moves.
238 fn move_prev(&mut self) -> Result<Action> {
239 Ok(Action::NoOp)
240 }
241
242 /// Handles a request to move the selection to the next logical item or element.
243 ///
244 /// This is often used for navigating forwards in a sequence (e.g., next tab, next item in a wizard) that may not
245 /// map directly to simple directional moves.
246 fn move_next(&mut self) -> Result<Action> {
247 Ok(Action::NoOp)
248 }
249
250 /// Handles a request to move the selection to the beginning (e.g., "Home" key).
251 ///
252 /// The `absolute` parameter indicates whether the movement should be absolute (to the very start of the component)
253 /// or relative (to the start of the current logical section).
254 ///
255 /// This typically moves the selection to the first item in a list, the start of a text input, or the first element
256 /// in a navigable group.
257 fn move_home(&mut self, absolute: bool) -> Result<Action> {
258 let _ = absolute;
259 Ok(Action::NoOp)
260 }
261
262 /// Handles a request to move the selection to the end (e.g., "End" key).
263 ///
264 /// The `absolute` parameter indicates whether the movement should be absolute (to the very end of the component) or
265 /// relative (to the end of the current logical section).
266 ///
267 /// This typically moves the selection to the last item in a list, the end of a text input, or the last element in a
268 /// navigable group.
269 fn move_end(&mut self, absolute: bool) -> Result<Action> {
270 let _ = absolute;
271 Ok(Action::NoOp)
272 }
273
274 /// Handles a request to undo the last action performed in the component.
275 ///
276 /// The specific behavior depends on the component's nature (e.g., undoing a text edit, reverting a selection
277 /// change).
278 fn undo(&mut self) -> Result<Action> {
279 Ok(Action::NoOp)
280 }
281
282 /// Handles a request to redo the last undone action in the component.
283 ///
284 /// The specific behavior depends on the component's nature (e.g., redoing a text edit, restoring a selection
285 /// change).
286 fn redo(&mut self) -> Result<Action> {
287 Ok(Action::NoOp)
288 }
289
290 /// Handles the insertion of a block of text into the component.
291 ///
292 /// This is typically used for pasting text into a focused input field.
293 /// If the component or its currently focused element does not support text input, this method may do nothing.
294 fn insert_text(&mut self, text: String) -> Result<Action> {
295 _ = text;
296 Ok(Action::NoOp)
297 }
298
299 /// Handles the insertion of a single character into the component.
300 ///
301 /// This is typically used for typing into a focused input field.
302 /// If the component or its currently focused element does not support text input, this method may do nothing.
303 fn insert_char(&mut self, c: char) -> Result<Action> {
304 _ = c;
305 Ok(Action::NoOp)
306 }
307
308 /// Handles a request to insert a newline character into the component.
309 ///
310 /// This is typically used for multiline text inputs or text areas where pressing "Shift+Enter" should create a new
311 /// line.
312 fn insert_newline(&mut self) -> Result<Action> {
313 Ok(Action::NoOp)
314 }
315
316 /// Handles the deletion key from the component, typically from a focused input field.
317 ///
318 /// The `backspace` parameter distinguishes between deleting the character before the cursor (backspace) and
319 /// deleting the character at/after the cursor (delete).
320 ///
321 /// The `word` parameter indicates whether the deletion should be applied to a whole word or just a single
322 /// character.
323 fn delete(&mut self, backspace: bool, word: bool) -> Result<Action> {
324 _ = backspace;
325 _ = word;
326 Ok(Action::NoOp)
327 }
328
329 /// Handles a request to delete the currently selected item or element within the component.
330 ///
331 /// The exact behavior depends on the component (e.g., deleting an item from a list, clearing a field).
332 async fn selection_delete(&mut self) -> Result<Action> {
333 Ok(Action::NoOp)
334 }
335
336 /// Handles a request to update or modify the currently selected item or element.
337 ///
338 /// This could mean initiating an edit mode for an item, toggling a state, or triggering some other modification.
339 async fn selection_update(&mut self) -> Result<Action> {
340 Ok(Action::NoOp)
341 }
342
343 /// Handles a request to confirm the currently selected item or element.
344 ///
345 /// This is often equivalent to an "Enter" key press on a selected item, triggering its primary action (e.g.,
346 /// executing a command, submitting a form item, navigating into a sub-menu).
347 async fn selection_confirm(&mut self) -> Result<Action> {
348 Ok(Action::NoOp)
349 }
350
351 /// Handles a request to execute the primary action associated with the currently selected item or element within
352 /// the component.
353 ///
354 /// This method is typically invoked when the user wants to "run" or "activate"
355 /// the selected item. For example, this could mean:
356 /// - Executing a shell command that is currently selected in a list.
357 /// - Starting a process associated with the selected item.
358 /// - Triggering a significant, non-trivial operation.
359 ///
360 /// The specific behavior is determined by the component and the nature of its items.
361 async fn selection_execute(&mut self) -> Result<Action> {
362 Ok(Action::NoOp)
363 }
364
365 /// Prompts an AI model about the component or highlighted element
366 async fn prompt_ai(&mut self) -> Result<Action> {
367 Ok(Action::NoOp)
368 }
369
370 /// For the search command only, toggle the search mode
371 fn toggle_search_mode(&mut self) -> Result<Action> {
372 Ok(Action::NoOp)
373 }
374
375 /// For the search command only, toggle the user-only mode
376 fn toggle_search_user_only(&mut self) -> Result<Action> {
377 Ok(Action::NoOp)
378 }
379}
380
381/// A placeholder component that provides no-op implementations for the [Component] trait.
382///
383/// This component is useful as a default or when no interactive component is currently active in the TUI.
384pub struct EmptyComponent;
385impl Component for EmptyComponent {
386 fn name(&self) -> &'static str {
387 "EmptyComponent"
388 }
389
390 fn min_inline_height(&self) -> u16 {
391 0
392 }
393
394 fn render(&mut self, _frame: &mut Frame, _area: Rect) {}
395}