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 edit;
13pub mod search;
14pub mod variable;
15
16/// Defines the behavior for a UI component within the application.
17///
18/// Components are responsible for rendering themselves, handling user input, and managing their internal state. They
19/// can also perform logic updates periodically via the `tick` method.
20#[async_trait]
21pub trait Component: Send {
22    /// Retrieves the component name, for debugging purposes
23    fn name(&self) -> &'static str;
24
25    /// Calculates the minimum height required by this component to be rendered correctly when inline (in rows)
26    fn min_inline_height(&self) -> u16;
27
28    /// Allows the component to initialize any internal state or resources it needs before being used.
29    ///
30    /// It can be called multiple times, for example if a component is re-used after being switched out.
31    async fn init(&mut self) -> Result<()> {
32        Ok(())
33    }
34
35    /// Peeks into the component before rendering, for examplle to give a straight result, switch component or continue
36    /// with the TUI
37    async fn peek(&mut self) -> Result<Action> {
38        Ok(Action::NoOp)
39    }
40
41    /// Processes time-based logic, internal state updates, or background tasks for the component.
42    ///
43    /// This method is called periodically by the application's main loop and is not directly tied to rendering or user
44    /// input events.
45    /// It can be used for animations, polling asynchronous operations, or updating internal timers.
46    fn tick(&mut self) -> Result<Action> {
47        Ok(Action::NoOp)
48    }
49
50    /// Renders the component's UI within the given `area` of the `frame`
51    fn render(&mut self, frame: &mut Frame, area: Rect);
52
53    /// Finalizes the component's current operation and returns its output with the current state.
54    ///
55    /// This method is typically called when the user signals that they want to exit the command.
56    fn exit(&mut self) -> Result<Option<ProcessOutput>> {
57        Ok(Some(ProcessOutput::success()))
58    }
59
60    /// Processes a paste event, typically from clipboard paste into the terminal.
61    ///
62    /// This method is called when the application detects a paste action. The `content` parameter contains the string
63    /// of text that was pasted.
64    ///
65    /// The default implementation will just call [`insert_text`](Component::insert_text).
66    fn process_paste_event(&mut self, content: String) -> Result<Action> {
67        self.insert_text(content)
68    }
69
70    /// Processes a key press event.
71    ///
72    /// This method is the primary handler for keyboard input. It receives a `KeyEvent` and is responsible for
73    /// translating it into component-specific behaviors or application-level [`Action`]s by consulting the provided
74    /// `keybindings` or using hardcoded defaults.
75    ///
76    /// Implementors can override this method to provide entirely custom key handling, optionally calling
77    /// [`default_process_key_event`](Component::default_process_key_event) first and checking its result.
78    async fn process_key_event(&mut self, keybindings: &KeyBindingsConfig, key: KeyEvent) -> Result<Action> {
79        Ok(self
80            .default_process_key_event(keybindings, key)
81            .await?
82            .unwrap_or_default())
83    }
84
85    /// The default behavior for [`process_key_event`](Component::process_key_event) with a baseline set of key mappings
86    /// by matching against the event. It calls other granular methods of this trait (e.g.,
87    /// [`move_up`](Component::move_up), [`insert_char`](Component::insert_char)).
88    ///
89    /// - If this default method returns `Ok(Some(action))`, it means the key was recognized and mapped to an `Action`.
90    /// - If it returns `Ok(None)`, it means the specific key event was **not handled** by this default logic, allowing
91    ///   an overriding implementation to then process it.
92    async fn default_process_key_event(
93        &mut self,
94        keybindings: &KeyBindingsConfig,
95        key: KeyEvent,
96    ) -> Result<Option<Action>> {
97        // Check customizable key bindings first
98        if let Some(action) = keybindings.get_action_matching(&key) {
99            return Ok(Some(match action {
100                KeyBindingAction::Quit => self.exit()?.map(Action::Quit).unwrap_or_default(),
101                KeyBindingAction::Update => self.selection_update().await?,
102                KeyBindingAction::Delete => self.selection_delete().await?,
103                KeyBindingAction::Confirm => self.selection_confirm().await?,
104                KeyBindingAction::Execute => self.selection_execute().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    /// For the search command only, toggle the search mode
366    fn toggle_search_mode(&mut self) -> Result<Action> {
367        Ok(Action::NoOp)
368    }
369
370    /// For the search command only, toggle the user-only mode
371    fn toggle_search_user_only(&mut self) -> Result<Action> {
372        Ok(Action::NoOp)
373    }
374}
375
376/// A placeholder component that provides no-op implementations for the [Component] trait.
377///
378/// This component is useful as a default or when no interactive component is currently active in the TUI.
379pub struct EmptyComponent;
380impl Component for EmptyComponent {
381    fn name(&self) -> &'static str {
382        "EmptyComponent"
383    }
384
385    fn min_inline_height(&self) -> u16 {
386        0
387    }
388
389    fn render(&mut self, _frame: &mut Frame, _area: Rect) {}
390}