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