Skip to main content

sql_cli/ui/input/
actions.rs

1// Action system for UI operations
2// This will gradually replace direct key handling in TUI
3
4use crate::app_state_container::SelectionMode;
5use crate::buffer::AppMode;
6use anyhow::Result;
7
8/// Where to position the cursor when switching to Command mode
9#[derive(Debug, Clone, PartialEq)]
10pub enum CursorPosition {
11    /// Keep cursor at current position
12    Current,
13    /// Move cursor to end of input
14    End,
15    /// Move cursor after a specific SQL clause
16    AfterClause(SqlClause),
17}
18
19/// SQL clauses that can be targeted for cursor positioning
20#[derive(Debug, Clone, PartialEq)]
21pub enum SqlClause {
22    Select,
23    From,
24    Where,
25    OrderBy,
26    GroupBy,
27    Having,
28    Limit,
29}
30
31/// All possible actions that can be triggered in the UI
32#[derive(Debug, Clone, PartialEq)]
33pub enum Action {
34    // Navigation actions
35    Navigate(NavigateAction),
36
37    // Mode switching
38    SwitchMode(AppMode),
39    SwitchModeWithCursor(AppMode, CursorPosition),
40    ToggleSelectionMode,
41    ExitCurrentMode,
42
43    // Editing actions
44    InsertChar(char),
45    Backspace,
46    Delete,
47    ClearLine,
48    Undo,
49    Redo,
50
51    // Cursor movement
52    MoveCursorLeft,
53    MoveCursorRight,
54    MoveCursorHome,
55    MoveCursorEnd,
56    MoveCursorWordLeft,
57    MoveCursorWordRight,
58    JumpToPrevToken,
59    JumpToNextToken,
60
61    // Text deletion
62    DeleteWordBackward,
63    DeleteWordForward,
64    DeleteToLineStart,
65    DeleteToLineEnd,
66
67    // Clipboard/Yank operations
68    Yank(YankTarget),
69    Paste,
70
71    // Column operations
72    ToggleColumnPin,
73    HideColumn,
74    UnhideAllColumns,
75    HideEmptyColumns,
76    MoveColumnLeft,
77    MoveColumnRight,
78    ClearAllPins,
79    NextColumn,
80    PreviousColumn,
81    ToggleCompactMode,
82    ToggleRowNumbers,
83
84    // Data operations
85    Sort(Option<usize>), // None = current column
86    StartFilter,
87    StartFuzzyFilter,
88    ApplyFilter(String),
89    ClearFilter,
90    ExportToCsv,
91    ExportToJson,
92
93    // Search operations
94    StartSearch,
95    StartColumnSearch,
96    NextMatch,
97    PreviousMatch,
98    NextSearchMatch,     // For 'n' key
99    PreviousSearchMatch, // For 'N' key
100
101    // Query operations
102    ExecuteQuery,
103    LoadFromHistory(usize),
104    StartHistorySearch,
105    PreviousHistoryCommand, // Ctrl+P, Alt+Up
106    NextHistoryCommand,     // Ctrl+N, Alt+Down
107
108    // View operations
109    RefreshView,
110    ShowHelp,
111    ShowDebugInfo,
112    ShowPrettyQuery,
113    StartJumpToRow,
114    NavigateToViewportTop,
115    NavigateToViewportMiddle,
116    NavigateToViewportBottom,
117    ToggleCursorLock,
118    ToggleViewportLock,
119    ToggleCaseInsensitive,
120    ToggleKeyIndicator,
121    ShowColumnStatistics, // For 'S' key
122    CycleColumnPacking,   // For Alt-S
123
124    // Text editing operations
125    KillLine,              // F9 - delete from cursor to end
126    KillLineBackward,      // F10 - delete from cursor to beginning
127    ExpandAsterisk,        // Expand * to column names
128    ExpandAsteriskVisible, // Expand * to visible column names only
129
130    // Application control
131    Quit,
132    ForceQuit,
133}
134
135/// Navigation actions with optional counts for vim-style motions
136#[derive(Debug, Clone, PartialEq)]
137pub enum NavigateAction {
138    Up(usize),
139    Down(usize),
140    Left(usize),
141    Right(usize),
142    PageUp,
143    PageDown,
144    Home,
145    End,
146    FirstColumn,
147    LastColumn,
148    JumpToRow(usize),
149    JumpToColumn(usize),
150}
151
152/// Targets for yank operations
153#[derive(Debug, Clone, PartialEq)]
154pub enum YankTarget {
155    Cell,
156    Row,
157    Column,
158    All,
159    Query,
160}
161
162/// Context needed to determine action availability and behavior
163#[derive(Debug, Clone)]
164pub struct ActionContext {
165    pub mode: AppMode,
166    pub selection_mode: SelectionMode,
167    pub has_results: bool,
168    pub has_filter: bool,
169    pub has_search: bool,
170    pub row_count: usize,
171    pub column_count: usize,
172    pub current_row: usize,
173    pub current_column: usize,
174}
175
176/// Result of handling an action
177#[derive(Debug, Clone, PartialEq)]
178pub enum ActionResult {
179    /// Action was handled successfully
180    Handled,
181    /// Action was not applicable in current context
182    NotHandled,
183    /// Action should cause application exit
184    Exit,
185    /// Action requires mode switch
186    SwitchMode(AppMode),
187    /// Action failed with error
188    Error(String),
189}
190
191/// Trait for components that can handle actions
192pub trait ActionHandler {
193    /// Check if this handler can process the given action in the current context
194    fn can_handle(&self, action: &Action, context: &ActionContext) -> bool;
195
196    /// Handle the action, returning the result
197    fn handle(&mut self, action: Action, context: &ActionContext) -> Result<ActionResult>;
198}
199
200/// Default implementation for checking action availability
201#[must_use]
202pub fn can_perform_action(action: &Action, context: &ActionContext) -> bool {
203    match action {
204        // Navigation is usually available in Results mode
205        Action::Navigate(_) => context.mode == AppMode::Results && context.has_results,
206
207        // Mode switching depends on current mode
208        Action::ToggleSelectionMode => context.mode == AppMode::Results,
209        Action::ExitCurrentMode => context.mode != AppMode::Command,
210
211        // Yank operations need results
212        Action::Yank(_) => context.has_results,
213
214        // Filter operations
215        Action::StartFilter | Action::StartFuzzyFilter => {
216            context.mode == AppMode::Results || context.mode == AppMode::Command
217        }
218        Action::ClearFilter => context.has_filter,
219
220        // Search operations
221        Action::NextMatch | Action::PreviousMatch => context.has_search,
222
223        // Most others depend on specific contexts
224        _ => true,
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn test_navigation_action_creation() {
234        let action = Action::Navigate(NavigateAction::Down(5));
235        assert_eq!(action, Action::Navigate(NavigateAction::Down(5)));
236    }
237
238    #[test]
239    fn test_action_context() {
240        let context = ActionContext {
241            mode: AppMode::Results,
242            selection_mode: SelectionMode::Row,
243            has_results: true,
244            has_filter: false,
245            has_search: false,
246            row_count: 100,
247            column_count: 10,
248            current_row: 0,
249            current_column: 0,
250        };
251
252        // Navigation should be available in Results mode with results
253        assert!(can_perform_action(
254            &Action::Navigate(NavigateAction::Down(1)),
255            &context
256        ));
257
258        // Filter clearing should not be available without active filter
259        assert!(!can_perform_action(&Action::ClearFilter, &context));
260    }
261}