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                KeyBindingAction::VariableNext => self.move_next_variable()?,
108                KeyBindingAction::VariablePrev => self.move_prev_variable()?,
109            }));
110        }
111
112        // If no configured binding matched, fall back to default handling
113        Ok(match key.code {
114            #[cfg(debug_assertions)]
115            // Debug
116            KeyCode::Char('p') if key.modifiers == KeyModifiers::ALT => panic!("Debug panic!"),
117            // Selection / Movement
118            KeyCode::Char('k') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_prev()?),
119            KeyCode::Char('j') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_next()?),
120            KeyCode::Home => Some(self.move_home(key.modifiers == KeyModifiers::CONTROL)?),
121            KeyCode::Char('a') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_home(false)?),
122            KeyCode::End => Some(self.move_end(key.modifiers == KeyModifiers::CONTROL)?),
123            KeyCode::Char('e') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_end(false)?),
124            KeyCode::Up => Some(self.move_up()?),
125            KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_up()?),
126            KeyCode::Down => Some(self.move_down()?),
127            KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_down()?),
128            KeyCode::Right => Some(self.move_right(key.modifiers == KeyModifiers::CONTROL)?),
129            KeyCode::Char('f') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_right(false)?),
130            KeyCode::Char('f') if key.modifiers == KeyModifiers::ALT => Some(self.move_right(true)?),
131            KeyCode::Left => Some(self.move_left(key.modifiers == KeyModifiers::CONTROL)?),
132            KeyCode::Char('b') if key.modifiers == KeyModifiers::CONTROL => Some(self.move_left(false)?),
133            KeyCode::Char('b') if key.modifiers == KeyModifiers::ALT => Some(self.move_left(true)?),
134            // Undo / redo
135            KeyCode::Char('z') if key.modifiers == KeyModifiers::CONTROL => Some(self.undo()?),
136            KeyCode::Char('u') if key.modifiers == KeyModifiers::CONTROL => Some(self.undo()?),
137            KeyCode::Char('y') if key.modifiers == KeyModifiers::CONTROL => Some(self.redo()?),
138            KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => Some(self.redo()?),
139            // Text edit
140            KeyCode::Backspace => Some(self.delete(true, key.modifiers == KeyModifiers::CONTROL)?),
141            KeyCode::Char('h') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(true, false)?),
142            KeyCode::Char('w') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(true, true)?),
143            KeyCode::Delete => Some(self.delete(false, key.modifiers == KeyModifiers::CONTROL)?),
144            KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => Some(self.delete(false, false)?),
145            KeyCode::Char('d') if key.modifiers == KeyModifiers::ALT => Some(self.delete(false, true)?),
146            KeyCode::Enter if key.modifiers == KeyModifiers::SHIFT => Some(self.insert_newline()?),
147            KeyCode::Enter if key.modifiers == KeyModifiers::ALT => Some(self.insert_newline()?),
148            KeyCode::Char('m') if key.modifiers == KeyModifiers::CONTROL => Some(self.insert_newline()?),
149            KeyCode::Char(c) => Some(self.insert_char(c)?),
150            // Don't process other events
151            _ => None,
152        })
153    }
154
155    /// Processes a mouse event.
156    ///
157    /// This method is called when a mouse action (like click, scroll, move) occurs within the terminal, provided mouse
158    /// capture is enabled.
159    ///
160    /// Returns [Some] if the event was processed or [None] if not (the default).
161    fn process_mouse_event(&mut self, mouse: MouseEvent) -> Result<Action> {
162        let _ = mouse;
163        Ok(Action::NoOp)
164    }
165
166    /// Called when the component gains focus within the application.
167    ///
168    /// Components can implement this method to change their appearance (e.g., show a highlight border), enable input
169    /// handling, initialize internal state specific to being active, or perform other setup tasks.
170    fn focus_gained(&mut self) -> Result<Action> {
171        Ok(Action::NoOp)
172    }
173
174    /// Called when the component loses focus within the application.
175    ///
176    /// Components can implement this method to change their appearance (e.g., remove a highlight border), disable input
177    /// handling, persist any temporary state, or perform other cleanup tasks.
178    fn focus_lost(&mut self) -> Result<Action> {
179        Ok(Action::NoOp)
180    }
181
182    /// Handles a terminal resize event, informing the component of the new global terminal dimensions.
183    ///
184    /// This method is called when the overall terminal window size changes.
185    /// Components can use this notification to adapt internal state, pre-calculate layout-dependent values, or
186    /// invalidate caches before a subsequent `render` call, which will likely provide a new drawing area (`Rect`)
187    /// based on these new terminal dimensions.
188    ///
189    /// **Note:** The `width` and `height` parameters typically represent the total new dimensions of the terminal in
190    /// columns and rows, not necessarily the area allocated to this specific component.
191    fn resize(&mut self, width: u16, height: u16) -> Result<Action> {
192        _ = (width, height);
193        Ok(Action::NoOp)
194    }
195
196    /// Handles a request to move the selection or focus upwards within the component.
197    ///
198    /// The exact behavior depends on the component's nature (e.g., moving up in a list, focusing an element above the
199    /// current one).
200    fn move_up(&mut self) -> Result<Action> {
201        Ok(Action::NoOp)
202    }
203
204    /// Handles a request to move the selection or focus downwards within the component.
205    ///
206    /// The exact behavior depends on the component's nature (e.g., moving down in a list, focusing an element below the
207    /// current one).
208    fn move_down(&mut self) -> Result<Action> {
209        Ok(Action::NoOp)
210    }
211
212    /// Handles a request to move the selection or focus to the left within the component.
213    ///
214    /// The `word` parameter indicates whether the movement should be applied to a whole word or just a single
215    /// character.
216    ///
217    /// The exact behavior depends on the component's nature (e.g., moving left in a text input, focusing an element to
218    /// the left of the current one).
219    fn move_left(&mut self, word: bool) -> Result<Action> {
220        let _ = word;
221        Ok(Action::NoOp)
222    }
223
224    /// Handles a request to move the selection or focus to the right within the component.
225    ///
226    /// The `word` parameter indicates whether the movement should be applied to a whole word or just a single
227    /// character.
228    ///
229    /// The exact behavior depends on the component's nature (e.g., moving right in a text input, focusing an element to
230    /// the right of the current one).
231    fn move_right(&mut self, word: bool) -> Result<Action> {
232        let _ = word;
233        Ok(Action::NoOp)
234    }
235
236    /// Handles a request to move the selection to the previous logical item or element.
237    ///
238    /// This is often used for navigating backwards in a sequence (e.g., previous tab,
239    /// previous item in a wizard) that may not map directly to simple directional moves.
240    fn move_prev(&mut self) -> Result<Action> {
241        Ok(Action::NoOp)
242    }
243
244    /// Handles a request to move the selection to the next logical item or element.
245    ///
246    /// This is often used for navigating forwards in a sequence (e.g., next tab, next item in a wizard) that may not
247    /// map directly to simple directional moves.
248    fn move_next(&mut self) -> Result<Action> {
249        Ok(Action::NoOp)
250    }
251
252    /// Handles a request to move to the previous variable in a template.
253    ///
254    /// This is used for navigating backwards through variables in commands, with wrapping from first to last variable.
255    fn move_prev_variable(&mut self) -> Result<Action> {
256        Ok(Action::NoOp)
257    }
258
259    /// Handles a request to move to the next variable in a template.
260    ///
261    /// This is used for navigating forwards through variables in commands, with wrapping from last to first variable.
262    fn move_next_variable(&mut self) -> Result<Action> {
263        Ok(Action::NoOp)
264    }
265
266    /// Handles a request to move the selection to the beginning (e.g., "Home" key).
267    ///
268    /// The `absolute` parameter indicates whether the movement should be absolute (to the very start of the component)
269    /// or relative (to the start of the current logical section).
270    ///
271    /// This typically moves the selection to the first item in a list, the start of a text input, or the first element
272    /// in a navigable group.
273    fn move_home(&mut self, absolute: bool) -> Result<Action> {
274        let _ = absolute;
275        Ok(Action::NoOp)
276    }
277
278    /// Handles a request to move the selection to the end (e.g., "End" key).
279    ///
280    /// The `absolute` parameter indicates whether the movement should be absolute (to the very end of the component) or
281    /// relative (to the end of the current logical section).
282    ///
283    /// This typically moves the selection to the last item in a list, the end of a text input, or the last element in a
284    /// navigable group.
285    fn move_end(&mut self, absolute: bool) -> Result<Action> {
286        let _ = absolute;
287        Ok(Action::NoOp)
288    }
289
290    /// Handles a request to undo the last action performed in the component.
291    ///
292    /// The specific behavior depends on the component's nature (e.g., undoing a text edit, reverting a selection
293    /// change).
294    fn undo(&mut self) -> Result<Action> {
295        Ok(Action::NoOp)
296    }
297
298    /// Handles a request to redo the last undone action in the component.
299    ///
300    /// The specific behavior depends on the component's nature (e.g., redoing a text edit, restoring a selection
301    /// change).
302    fn redo(&mut self) -> Result<Action> {
303        Ok(Action::NoOp)
304    }
305
306    /// Handles the insertion of a block of text into the component.
307    ///
308    /// This is typically used for pasting text into a focused input field.
309    /// If the component or its currently focused element does not support text input, this method may do nothing.
310    fn insert_text(&mut self, text: String) -> Result<Action> {
311        _ = text;
312        Ok(Action::NoOp)
313    }
314
315    /// Handles the insertion of a single character into the component.
316    ///
317    /// This is typically used for typing into a focused input field.
318    /// If the component or its currently focused element does not support text input, this method may do nothing.
319    fn insert_char(&mut self, c: char) -> Result<Action> {
320        _ = c;
321        Ok(Action::NoOp)
322    }
323
324    /// Handles a request to insert a newline character into the component.
325    ///
326    /// This is typically used for multiline text inputs or text areas where pressing "Shift+Enter" should create a new
327    /// line.
328    fn insert_newline(&mut self) -> Result<Action> {
329        Ok(Action::NoOp)
330    }
331
332    /// Handles the deletion key from the component, typically from a focused input field.
333    ///
334    /// The `backspace` parameter distinguishes between deleting the character before the cursor (backspace) and
335    /// deleting the character at/after the cursor (delete).
336    ///
337    /// The `word` parameter indicates whether the deletion should be applied to a whole word or just a single
338    /// character.
339    fn delete(&mut self, backspace: bool, word: bool) -> Result<Action> {
340        _ = backspace;
341        _ = word;
342        Ok(Action::NoOp)
343    }
344
345    /// Handles a request to delete the currently selected item or element within the component.
346    ///
347    /// The exact behavior depends on the component (e.g., deleting an item from a list, clearing a field).
348    async fn selection_delete(&mut self) -> Result<Action> {
349        Ok(Action::NoOp)
350    }
351
352    /// Handles a request to update or modify the currently selected item or element.
353    ///
354    /// This could mean initiating an edit mode for an item, toggling a state, or triggering some other modification.
355    async fn selection_update(&mut self) -> Result<Action> {
356        Ok(Action::NoOp)
357    }
358
359    /// Handles a request to confirm the currently selected item or element.
360    ///
361    /// This is often equivalent to an "Enter" key press on a selected item, triggering its primary action (e.g.,
362    /// executing a command, submitting a form item, navigating into a sub-menu).
363    async fn selection_confirm(&mut self) -> Result<Action> {
364        Ok(Action::NoOp)
365    }
366
367    /// Handles a request to execute the primary action associated with the currently selected item or element within
368    /// the component.
369    ///
370    /// This method is typically invoked when the user wants to "run" or "activate"
371    /// the selected item. For example, this could mean:
372    /// - Executing a shell command that is currently selected in a list.
373    /// - Starting a process associated with the selected item.
374    /// - Triggering a significant, non-trivial operation.
375    ///
376    /// The specific behavior is determined by the component and the nature of its items.
377    async fn selection_execute(&mut self) -> Result<Action> {
378        Ok(Action::NoOp)
379    }
380
381    /// Prompts an AI model about the component or highlighted element
382    async fn prompt_ai(&mut self) -> Result<Action> {
383        Ok(Action::NoOp)
384    }
385
386    /// For the search command only, toggle the search mode
387    fn toggle_search_mode(&mut self) -> Result<Action> {
388        Ok(Action::NoOp)
389    }
390
391    /// For the search command only, toggle the user-only mode
392    fn toggle_search_user_only(&mut self) -> Result<Action> {
393        Ok(Action::NoOp)
394    }
395}
396
397/// A placeholder component that provides no-op implementations for the [Component] trait.
398///
399/// This component is useful as a default or when no interactive component is currently active in the TUI.
400pub struct EmptyComponent;
401impl Component for EmptyComponent {
402    fn name(&self) -> &'static str {
403        "EmptyComponent"
404    }
405
406    fn min_inline_height(&self) -> u16 {
407        0
408    }
409
410    fn render(&mut self, _frame: &mut Frame, _area: Rect) {}
411}