sql_cli/
cursor_manager.rs1pub struct CursorManager {
4 input_cursor_position: usize,
6
7 _visual_cursor: (usize, usize),
9
10 table_cursor: (usize, usize),
12
13 horizontal_scroll: u16,
15
16 vertical_scroll: usize,
18}
19
20impl CursorManager {
21 pub fn new() -> Self {
22 Self {
23 input_cursor_position: 0,
24 _visual_cursor: (0, 0),
25 table_cursor: (0, 0),
26 horizontal_scroll: 0,
27 vertical_scroll: 0,
28 }
29 }
30
31 pub fn move_word_forward(&mut self, text: &str) -> usize {
35 let chars: Vec<char> = text.chars().collect();
36 let mut pos = self.input_cursor_position;
37
38 while pos < chars.len() && !chars[pos].is_whitespace() {
40 pos += 1;
41 }
42
43 while pos < chars.len() && chars[pos].is_whitespace() {
45 pos += 1;
46 }
47
48 self.input_cursor_position = pos;
49 pos
50 }
51
52 pub fn move_word_backward(&mut self, text: &str) -> usize {
54 let chars: Vec<char> = text.chars().collect();
55 let mut pos = self.input_cursor_position;
56
57 if pos == 0 {
58 return 0;
59 }
60
61 pos -= 1;
62
63 while pos > 0 && chars[pos].is_whitespace() {
65 pos -= 1;
66 }
67
68 while pos > 0 && !chars[pos - 1].is_whitespace() {
70 pos -= 1;
71 }
72
73 self.input_cursor_position = pos;
74 pos
75 }
76
77 pub fn move_to_line_start(&mut self) -> usize {
79 self.input_cursor_position = 0;
80 0
81 }
82
83 pub fn move_to_line_end(&mut self, text: &str) -> usize {
85 self.input_cursor_position = text.len();
86 text.len()
87 }
88
89 pub fn move_left(&mut self) -> usize {
91 if self.input_cursor_position > 0 {
92 self.input_cursor_position -= 1;
93 }
94 self.input_cursor_position
95 }
96
97 pub fn move_right(&mut self, text: &str) -> usize {
99 if self.input_cursor_position < text.len() {
100 self.input_cursor_position += 1;
101 }
102 self.input_cursor_position
103 }
104
105 pub fn set_position(&mut self, pos: usize) {
107 self.input_cursor_position = pos;
108 }
109
110 pub fn position(&self) -> usize {
112 self.input_cursor_position
113 }
114
115 pub fn move_table_up(&mut self) -> (usize, usize) {
119 if self.table_cursor.0 > 0 {
120 self.table_cursor.0 -= 1;
121 }
122 self.table_cursor
123 }
124
125 pub fn move_table_down(&mut self, max_rows: usize) -> (usize, usize) {
127 if self.table_cursor.0 < max_rows.saturating_sub(1) {
128 self.table_cursor.0 += 1;
129 }
130 self.table_cursor
131 }
132
133 pub fn move_table_left(&mut self) -> (usize, usize) {
135 if self.table_cursor.1 > 0 {
136 self.table_cursor.1 -= 1;
137 }
138 self.table_cursor
139 }
140
141 pub fn move_table_right(&mut self, max_cols: usize) -> (usize, usize) {
143 if self.table_cursor.1 < max_cols.saturating_sub(1) {
144 self.table_cursor.1 += 1;
145 }
146 self.table_cursor
147 }
148
149 pub fn move_table_home(&mut self) -> (usize, usize) {
151 self.table_cursor.0 = 0;
152 self.table_cursor
153 }
154
155 pub fn move_table_end(&mut self, max_rows: usize) -> (usize, usize) {
157 self.table_cursor.0 = max_rows.saturating_sub(1);
158 self.table_cursor
159 }
160
161 pub fn page_up(&mut self, page_size: usize) -> (usize, usize) {
163 self.table_cursor.0 = self.table_cursor.0.saturating_sub(page_size);
164 self.table_cursor
165 }
166
167 pub fn page_down(&mut self, page_size: usize, max_rows: usize) -> (usize, usize) {
169 self.table_cursor.0 = (self.table_cursor.0 + page_size).min(max_rows.saturating_sub(1));
170 self.table_cursor
171 }
172
173 pub fn table_position(&self) -> (usize, usize) {
175 self.table_cursor
176 }
177
178 pub fn reset_table_cursor(&mut self) {
180 self.table_cursor = (0, 0);
181 }
182
183 pub fn update_horizontal_scroll(&mut self, cursor_col: usize, viewport_width: u16) {
187 let cursor_x = cursor_col as u16;
188
189 if cursor_x >= self.horizontal_scroll + viewport_width {
191 self.horizontal_scroll = cursor_x.saturating_sub(viewport_width - 1);
192 }
193
194 if cursor_x < self.horizontal_scroll {
196 self.horizontal_scroll = cursor_x;
197 }
198 }
199
200 pub fn update_vertical_scroll(&mut self, cursor_row: usize, viewport_height: usize) {
202 if cursor_row >= self.vertical_scroll + viewport_height {
204 self.vertical_scroll = cursor_row.saturating_sub(viewport_height - 1);
205 }
206
207 if cursor_row < self.vertical_scroll {
209 self.vertical_scroll = cursor_row;
210 }
211 }
212
213 pub fn scroll_offsets(&self) -> (u16, usize) {
215 (self.horizontal_scroll, self.vertical_scroll)
216 }
217
218 pub fn set_scroll_offsets(&mut self, horizontal: u16, vertical: usize) {
220 self.horizontal_scroll = horizontal;
221 self.vertical_scroll = vertical;
222 }
223
224 pub fn reset_horizontal_scroll(&mut self) {
226 self.horizontal_scroll = 0;
227 }
228
229 pub fn get_word_at_cursor(&self, text: &str) -> Option<(usize, usize, String)> {
233 if text.is_empty() || self.input_cursor_position > text.len() {
234 return None;
235 }
236
237 let chars: Vec<char> = text.chars().collect();
238 let mut start = self.input_cursor_position;
239 let mut end = self.input_cursor_position;
240
241 if start < chars.len() && chars[start].is_whitespace() {
243 return None;
244 }
245
246 while start > 0 && !chars[start - 1].is_whitespace() {
248 start -= 1;
249 }
250
251 while end < chars.len() && !chars[end].is_whitespace() {
253 end += 1;
254 }
255
256 let word: String = chars[start..end].iter().collect();
257 Some((start, end, word))
258 }
259
260 pub fn get_partial_word_before_cursor(&self, text: &str) -> Option<String> {
262 if self.input_cursor_position == 0 {
263 return None;
264 }
265
266 let before_cursor = &text[..self.input_cursor_position];
267 let last_space = before_cursor.rfind(' ').map(|i| i + 1).unwrap_or(0);
268
269 if last_space < self.input_cursor_position {
270 Some(before_cursor[last_space..].to_string())
271 } else {
272 None
273 }
274 }
275}
276
277pub trait CursorBuffer {
279 fn cursor_manager(&self) -> &CursorManager;
280 fn cursor_manager_mut(&mut self) -> &mut CursorManager;
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_word_navigation() {
289 let mut cm = CursorManager::new();
290 let text = "SELECT * FROM table WHERE id = 1";
291
292 assert_eq!(cm.position(), 0);
294
295 cm.move_word_forward(text);
297 assert_eq!(cm.position(), 7); cm.move_word_forward(text);
300 assert_eq!(cm.position(), 9); cm.move_word_backward(text);
304 assert_eq!(cm.position(), 7); cm.move_word_backward(text);
307 assert_eq!(cm.position(), 0); }
309
310 #[test]
311 fn test_table_navigation() {
312 let mut cm = CursorManager::new();
313
314 cm.move_table_down(10);
316 assert_eq!(cm.table_position(), (1, 0));
317
318 cm.move_table_right(5);
319 assert_eq!(cm.table_position(), (1, 1));
320
321 cm.move_table_end(10);
323 assert_eq!(cm.table_position(), (9, 1));
324
325 cm.move_table_home();
326 assert_eq!(cm.table_position(), (0, 1));
327 }
328
329 #[test]
330 fn test_scroll_management() {
331 let mut cm = CursorManager::new();
332
333 cm.update_horizontal_scroll(100, 80);
335 assert_eq!(cm.scroll_offsets().0, 21); cm.update_vertical_scroll(50, 20);
339 assert_eq!(cm.scroll_offsets().1, 31); }
341
342 #[test]
343 fn test_word_extraction() {
344 let mut cm = CursorManager::new();
345 let text = "SELECT column FROM table";
346
347 cm.set_position(7);
349 let word = cm.get_word_at_cursor(text);
350 assert_eq!(word, Some((7, 13, "column".to_string())));
351
352 cm.set_position(6);
354 let word = cm.get_word_at_cursor(text);
355 assert_eq!(word, None);
356
357 cm.set_position(10); let partial = cm.get_partial_word_before_cursor(text);
360 assert_eq!(partial, Some("col".to_string()));
361 }
362}