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
59    // Text deletion
60    DeleteWordBackward,
61    DeleteWordForward,
62    DeleteToLineStart,
63    DeleteToLineEnd,
64
65    // Clipboard/Yank operations
66    Yank(YankTarget),
67    Paste,
68
69    // Column operations
70    ToggleColumnPin,
71    HideColumn,
72    UnhideAllColumns,
73    HideEmptyColumns,
74    MoveColumnLeft,
75    MoveColumnRight,
76    ClearAllPins,
77    NextColumn,
78    PreviousColumn,
79    ToggleCompactMode,
80    ToggleRowNumbers,
81
82    // Data operations
83    Sort(Option<usize>), // None = current column
84    StartFilter,
85    StartFuzzyFilter,
86    ApplyFilter(String),
87    ClearFilter,
88    ExportToCsv,
89    ExportToJson,
90
91    // Search operations
92    StartSearch,
93    StartColumnSearch,
94    NextMatch,
95    PreviousMatch,
96    NextSearchMatch,     // For 'n' key
97    PreviousSearchMatch, // For 'N' key
98
99    // Query operations
100    ExecuteQuery,
101    LoadFromHistory(usize),
102    StartHistorySearch,
103    PreviousHistoryCommand, // Ctrl+P, Alt+Up
104    NextHistoryCommand,     // Ctrl+N, Alt+Down
105
106    // View operations
107    RefreshView,
108    ShowHelp,
109    ShowDebugInfo,
110    ShowPrettyQuery,
111    StartJumpToRow,
112    NavigateToViewportTop,
113    NavigateToViewportMiddle,
114    NavigateToViewportBottom,
115    ToggleCursorLock,
116    ToggleViewportLock,
117    ToggleCaseInsensitive,
118    ToggleKeyIndicator,
119    ShowColumnStatistics, // For 'S' key
120    CycleColumnPacking,   // For Alt-S
121
122    // Text editing operations
123    KillLine,              // F9 - delete from cursor to end
124    KillLineBackward,      // F10 - delete from cursor to beginning
125    ExpandAsterisk,        // Expand * to column names
126    ExpandAsteriskVisible, // Expand * to visible column names only
127
128    // Application control
129    Quit,
130    ForceQuit,
131}
132
133/// Navigation actions with optional counts for vim-style motions
134#[derive(Debug, Clone, PartialEq)]
135pub enum NavigateAction {
136    Up(usize),
137    Down(usize),
138    Left(usize),
139    Right(usize),
140    PageUp,
141    PageDown,
142    Home,
143    End,
144    FirstColumn,
145    LastColumn,
146    JumpToRow(usize),
147    JumpToColumn(usize),
148}
149
150/// Targets for yank operations
151#[derive(Debug, Clone, PartialEq)]
152pub enum YankTarget {
153    Cell,
154    Row,
155    Column,
156    All,
157    Query,
158}
159
160/// Context needed to determine action availability and behavior
161#[derive(Debug, Clone)]
162pub struct ActionContext {
163    pub mode: AppMode,
164    pub selection_mode: SelectionMode,
165    pub has_results: bool,
166    pub has_filter: bool,
167    pub has_search: bool,
168    pub row_count: usize,
169    pub column_count: usize,
170    pub current_row: usize,
171    pub current_column: usize,
172}
173
174/// Result of handling an action
175#[derive(Debug, Clone, PartialEq)]
176pub enum ActionResult {
177    /// Action was handled successfully
178    Handled,
179    /// Action was not applicable in current context
180    NotHandled,
181    /// Action should cause application exit
182    Exit,
183    /// Action requires mode switch
184    SwitchMode(AppMode),
185    /// Action failed with error
186    Error(String),
187}
188
189/// Trait for components that can handle actions
190pub trait ActionHandler {
191    /// Check if this handler can process the given action in the current context
192    fn can_handle(&self, action: &Action, context: &ActionContext) -> bool;
193
194    /// Handle the action, returning the result
195    fn handle(&mut self, action: Action, context: &ActionContext) -> Result<ActionResult>;
196}
197
198/// Default implementation for checking action availability
199pub fn can_perform_action(action: &Action, context: &ActionContext) -> bool {
200    match action {
201        // Navigation is usually available in Results mode
202        Action::Navigate(_) => context.mode == AppMode::Results && context.has_results,
203
204        // Mode switching depends on current mode
205        Action::ToggleSelectionMode => context.mode == AppMode::Results,
206        Action::ExitCurrentMode => context.mode != AppMode::Command,
207
208        // Yank operations need results
209        Action::Yank(_) => context.has_results,
210
211        // Filter operations
212        Action::StartFilter | Action::StartFuzzyFilter => {
213            context.mode == AppMode::Results || context.mode == AppMode::Command
214        }
215        Action::ClearFilter => context.has_filter,
216
217        // Search operations
218        Action::NextMatch | Action::PreviousMatch => context.has_search,
219
220        // Most others depend on specific contexts
221        _ => true,
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_navigation_action_creation() {
231        let action = Action::Navigate(NavigateAction::Down(5));
232        assert_eq!(action, Action::Navigate(NavigateAction::Down(5)));
233    }
234
235    #[test]
236    fn test_action_context() {
237        let context = ActionContext {
238            mode: AppMode::Results,
239            selection_mode: SelectionMode::Row,
240            has_results: true,
241            has_filter: false,
242            has_search: false,
243            row_count: 100,
244            column_count: 10,
245            current_row: 0,
246            current_column: 0,
247        };
248
249        // Navigation should be available in Results mode with results
250        assert!(can_perform_action(
251            &Action::Navigate(NavigateAction::Down(1)),
252            &context
253        ));
254
255        // Filter clearing should not be available without active filter
256        assert!(!can_perform_action(&Action::ClearFilter, &context));
257    }
258}