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    #[must_use]
72    pub fn new(text: String) -> Self {
73        let input = Input::new(text.clone()).with_cursor(text.len());
74        Self {
75            input,
76            history: Vec::new(),
77            history_index: None,
78            temp_storage: None,
79        }
80    }
81
82    #[must_use]
83    pub fn from_input(input: Input) -> Self {
84        Self {
85            input,
86            history: Vec::new(),
87            history_index: None,
88            temp_storage: None,
89        }
90    }
91
92    #[must_use]
93    pub fn as_input(&self) -> &Input {
94        &self.input
95    }
96
97    pub fn as_input_mut(&mut self) -> &mut Input {
98        &mut self.input
99    }
100}
101
102impl InputManager for SingleLineInput {
103    fn get_text(&self) -> String {
104        self.input.value().to_string()
105    }
106
107    fn set_text(&mut self, text: String) {
108        let cursor_pos = text.len();
109        self.input = Input::new(text).with_cursor(cursor_pos);
110    }
111
112    fn get_cursor_position(&self) -> usize {
113        self.input.visual_cursor()
114    }
115
116    fn set_cursor_position(&mut self, position: usize) {
117        let text = self.input.value().to_string();
118        self.input = Input::new(text).with_cursor(position);
119    }
120
121    fn handle_key_event(&mut self, event: KeyEvent) -> bool {
122        // Convert KeyEvent to Event for tui_input
123        let crossterm_event = Event::Key(event);
124        self.input.handle_event(&crossterm_event);
125        true
126    }
127
128    fn clear(&mut self) {
129        self.input = Input::default();
130    }
131
132    fn is_empty(&self) -> bool {
133        self.input.value().is_empty()
134    }
135
136    fn get_visual_cursor(&self) -> (u16, u16) {
137        (0, self.input.visual_cursor() as u16)
138    }
139
140    fn is_multiline(&self) -> bool {
141        false
142    }
143
144    fn line_count(&self) -> usize {
145        1
146    }
147
148    fn get_line(&self, index: usize) -> Option<String> {
149        if index == 0 {
150            Some(self.get_text())
151        } else {
152            None
153        }
154    }
155
156    fn clone_box(&self) -> Box<dyn InputManager> {
157        Box::new(SingleLineInput {
158            input: self.input.clone(),
159            history: self.history.clone(),
160            history_index: self.history_index,
161            temp_storage: self.temp_storage.clone(),
162        })
163    }
164
165    // --- History Navigation ---
166
167    fn set_history(&mut self, history: Vec<String>) {
168        self.history = history;
169        self.history_index = None;
170        self.temp_storage = None;
171    }
172
173    fn history_previous(&mut self) -> bool {
174        if self.history.is_empty() {
175            return false;
176        }
177
178        match self.history_index {
179            None => {
180                // First time navigating, save current input
181                self.temp_storage = Some(self.input.value().to_string());
182                self.history_index = Some(self.history.len() - 1);
183            }
184            Some(0) => return false, // Already at oldest
185            Some(idx) => {
186                self.history_index = Some(idx - 1);
187            }
188        }
189
190        // Update input with history entry
191        if let Some(idx) = self.history_index {
192            if let Some(entry) = self.history.get(idx) {
193                let len = entry.len();
194                self.input = Input::new(entry.clone()).with_cursor(len);
195                return true;
196            }
197        }
198        false
199    }
200
201    fn history_next(&mut self) -> bool {
202        match self.history_index {
203            None => false, // Not navigating history
204            Some(idx) => {
205                if idx >= self.history.len() - 1 {
206                    // Going back to user input
207                    if let Some(temp) = &self.temp_storage {
208                        let len = temp.len();
209                        self.input = Input::new(temp.clone()).with_cursor(len);
210                    }
211                    self.history_index = None;
212                    self.temp_storage = None;
213                    true
214                } else {
215                    self.history_index = Some(idx + 1);
216                    if let Some(entry) = self.history.get(idx + 1) {
217                        let len = entry.len();
218                        self.input = Input::new(entry.clone()).with_cursor(len);
219                        true
220                    } else {
221                        false
222                    }
223                }
224            }
225        }
226    }
227
228    fn get_history_index(&self) -> Option<usize> {
229        self.history_index
230    }
231
232    fn reset_history_position(&mut self) {
233        self.history_index = None;
234        self.temp_storage = None;
235    }
236}
237
238/// Factory methods for creating `InputManager` instances
239#[must_use]
240pub fn create_single_line(text: String) -> Box<dyn InputManager> {
241    Box::new(SingleLineInput::new(text))
242}
243
244#[must_use]
245pub fn create_from_input(input: Input) -> Box<dyn InputManager> {
246    Box::new(SingleLineInput::from_input(input))
247}