sql_cli/
input_manager.rs

1use crossterm::event::{Event, KeyEvent};
2use tui_input::{backend::crossterm::EventHandler, Input};
3
4/// Unified interface for managing input widgets (Input and TextArea)
5/// This trait abstracts the differences between single-line and multi-line input
6/// allowing the Buffer system to work with either transparently
7pub trait InputManager: Send + Sync {
8    /// Get the current text content
9    fn get_text(&self) -> String;
10
11    /// Set the text content
12    fn set_text(&mut self, text: String);
13
14    /// Get the current cursor position (char offset from start)
15    fn get_cursor_position(&self) -> usize;
16
17    /// Set the cursor position (char offset from start)
18    fn set_cursor_position(&mut self, position: usize);
19
20    /// Handle a key event
21    fn handle_key_event(&mut self, event: KeyEvent) -> bool;
22
23    /// Clear the content
24    fn clear(&mut self);
25
26    /// Check if content is empty
27    fn is_empty(&self) -> bool;
28
29    /// Get the visual cursor position for rendering (row, col)
30    fn get_visual_cursor(&self) -> (u16, u16);
31
32    /// Check if this is a multi-line input
33    fn is_multiline(&self) -> bool;
34
35    /// Get line count (1 for single-line)
36    fn line_count(&self) -> usize;
37
38    /// Get a specific line of text (0-indexed)
39    fn get_line(&self, index: usize) -> Option<String>;
40
41    /// Clone the input manager (for undo/redo)
42    fn clone_box(&self) -> Box<dyn InputManager>;
43
44    // --- History Navigation ---
45
46    /// Set the history entries for navigation
47    fn set_history(&mut self, history: Vec<String>);
48
49    /// Navigate to previous history entry (returns true if navigation occurred)
50    fn history_previous(&mut self) -> bool;
51
52    /// Navigate to next history entry (returns true if navigation occurred)
53    fn history_next(&mut self) -> bool;
54
55    /// Get current history index (None if not navigating history)
56    fn get_history_index(&self) -> Option<usize>;
57
58    /// Reset history navigation (go back to user input)
59    fn reset_history_position(&mut self);
60}
61
62/// Single-line input manager wrapping tui_input::Input
63pub struct SingleLineInput {
64    input: Input,
65    history: Vec<String>,
66    history_index: Option<usize>,
67    temp_storage: Option<String>, // Store user input when navigating history
68}
69
70impl SingleLineInput {
71    pub fn new(text: String) -> Self {
72        let input = Input::new(text.clone()).with_cursor(text.len());
73        Self {
74            input,
75            history: Vec::new(),
76            history_index: None,
77            temp_storage: None,
78        }
79    }
80
81    pub fn from_input(input: Input) -> Self {
82        Self {
83            input,
84            history: Vec::new(),
85            history_index: None,
86            temp_storage: None,
87        }
88    }
89
90    pub fn as_input(&self) -> &Input {
91        &self.input
92    }
93
94    pub fn as_input_mut(&mut self) -> &mut Input {
95        &mut self.input
96    }
97}
98
99impl InputManager for SingleLineInput {
100    fn get_text(&self) -> String {
101        self.input.value().to_string()
102    }
103
104    fn set_text(&mut self, text: String) {
105        let cursor_pos = text.len();
106        self.input = Input::new(text).with_cursor(cursor_pos);
107    }
108
109    fn get_cursor_position(&self) -> usize {
110        self.input.visual_cursor()
111    }
112
113    fn set_cursor_position(&mut self, position: usize) {
114        let text = self.input.value().to_string();
115        self.input = Input::new(text).with_cursor(position);
116    }
117
118    fn handle_key_event(&mut self, event: KeyEvent) -> bool {
119        // Convert KeyEvent to Event for tui_input
120        let crossterm_event = Event::Key(event);
121        self.input.handle_event(&crossterm_event);
122        true
123    }
124
125    fn clear(&mut self) {
126        self.input = Input::default();
127    }
128
129    fn is_empty(&self) -> bool {
130        self.input.value().is_empty()
131    }
132
133    fn get_visual_cursor(&self) -> (u16, u16) {
134        (0, self.input.visual_cursor() as u16)
135    }
136
137    fn is_multiline(&self) -> bool {
138        false
139    }
140
141    fn line_count(&self) -> usize {
142        1
143    }
144
145    fn get_line(&self, index: usize) -> Option<String> {
146        if index == 0 {
147            Some(self.get_text())
148        } else {
149            None
150        }
151    }
152
153    fn clone_box(&self) -> Box<dyn InputManager> {
154        Box::new(SingleLineInput {
155            input: self.input.clone(),
156            history: self.history.clone(),
157            history_index: self.history_index,
158            temp_storage: self.temp_storage.clone(),
159        })
160    }
161
162    // --- History Navigation ---
163
164    fn set_history(&mut self, history: Vec<String>) {
165        self.history = history;
166        self.history_index = None;
167        self.temp_storage = None;
168    }
169
170    fn history_previous(&mut self) -> bool {
171        if self.history.is_empty() {
172            return false;
173        }
174
175        match self.history_index {
176            None => {
177                // First time navigating, save current input
178                self.temp_storage = Some(self.input.value().to_string());
179                self.history_index = Some(self.history.len() - 1);
180            }
181            Some(0) => return false, // Already at oldest
182            Some(idx) => {
183                self.history_index = Some(idx - 1);
184            }
185        }
186
187        // Update input with history entry
188        if let Some(idx) = self.history_index {
189            if let Some(entry) = self.history.get(idx) {
190                let len = entry.len();
191                self.input = Input::new(entry.clone()).with_cursor(len);
192                return true;
193            }
194        }
195        false
196    }
197
198    fn history_next(&mut self) -> bool {
199        match self.history_index {
200            None => false, // Not navigating history
201            Some(idx) => {
202                if idx >= self.history.len() - 1 {
203                    // Going back to user input
204                    if let Some(temp) = &self.temp_storage {
205                        let len = temp.len();
206                        self.input = Input::new(temp.clone()).with_cursor(len);
207                    }
208                    self.history_index = None;
209                    self.temp_storage = None;
210                    true
211                } else {
212                    self.history_index = Some(idx + 1);
213                    if let Some(entry) = self.history.get(idx + 1) {
214                        let len = entry.len();
215                        self.input = Input::new(entry.clone()).with_cursor(len);
216                        true
217                    } else {
218                        false
219                    }
220                }
221            }
222        }
223    }
224
225    fn get_history_index(&self) -> Option<usize> {
226        self.history_index
227    }
228
229    fn reset_history_position(&mut self) {
230        self.history_index = None;
231        self.temp_storage = None;
232    }
233}
234
235/// Factory methods for creating InputManager instances
236pub fn create_single_line(text: String) -> Box<dyn InputManager> {
237    Box::new(SingleLineInput::new(text))
238}
239
240pub fn create_from_input(input: Input) -> Box<dyn InputManager> {
241    Box::new(SingleLineInput::from_input(input))
242}