sql_cli/
cursor_operations.rs1use crate::recursive_parser::Lexer;
5
6pub struct CursorOperations;
7
8impl CursorOperations {
9 #[must_use]
11 pub fn find_word_boundary_backward(text: &str, cursor_pos: usize) -> usize {
12 if cursor_pos == 0 {
13 return 0;
14 }
15
16 let mut lexer = Lexer::new(text);
18 let tokens = lexer.tokenize_all_with_positions();
19
20 let mut target_pos = 0;
22 for (start, end, _) in tokens.iter().rev() {
23 if *end <= cursor_pos {
24 if *end == cursor_pos && start < &cursor_pos {
26 target_pos = *start;
27 } else {
28 for (s, e, _) in tokens.iter().rev() {
30 if *e <= cursor_pos && *s < cursor_pos {
31 target_pos = *s;
32 break;
33 }
34 }
35 }
36 break;
37 }
38 }
39
40 target_pos
41 }
42
43 #[must_use]
45 pub fn find_word_boundary_forward(text: &str, cursor_pos: usize) -> usize {
46 let mut lexer = Lexer::new(text);
48 let tokens = lexer.tokenize_all_with_positions();
49
50 for (start, _, _) in &tokens {
52 if *start > cursor_pos {
53 return *start;
54 }
55 }
56
57 text.len()
59 }
60
61 #[must_use]
63 pub fn delete_word_backward(text: &str, cursor_pos: usize) -> (String, usize) {
64 if cursor_pos == 0 {
65 return (text.to_string(), cursor_pos);
66 }
67
68 let word_start = Self::find_word_boundary_backward(text, cursor_pos);
69
70 let mut new_text = String::new();
72 new_text.push_str(&text[..word_start]);
73 new_text.push_str(&text[cursor_pos..]);
74
75 (new_text, word_start)
76 }
77
78 #[must_use]
80 pub fn delete_word_forward(text: &str, cursor_pos: usize) -> (String, usize) {
81 if cursor_pos >= text.len() {
82 return (text.to_string(), cursor_pos);
83 }
84
85 let word_end = Self::find_word_boundary_forward(text, cursor_pos);
86
87 let mut new_text = String::new();
89 new_text.push_str(&text[..cursor_pos]);
90 new_text.push_str(&text[word_end..]);
91
92 (new_text, cursor_pos)
93 }
94
95 #[must_use]
97 pub fn kill_line(text: &str, cursor_pos: usize) -> (String, String) {
98 let killed = text[cursor_pos..].to_string();
99 let new_text = text[..cursor_pos].to_string();
100 (new_text, killed)
101 }
102
103 #[must_use]
105 pub fn kill_line_backward(text: &str, cursor_pos: usize) -> (String, String, usize) {
106 let killed = text[..cursor_pos].to_string();
107 let new_text = text[cursor_pos..].to_string();
108 (new_text, killed, 0) }
110
111 #[must_use]
113 pub fn jump_to_prev_token(text: &str, cursor_pos: usize) -> usize {
114 let mut lexer = Lexer::new(text);
115 let tokens = lexer.tokenize_all_with_positions();
116
117 let mut target_pos = cursor_pos;
119 for (start, _, _) in tokens.iter().rev() {
120 if *start < cursor_pos {
121 target_pos = *start;
122 break;
123 }
124 }
125
126 target_pos
127 }
128
129 #[must_use]
131 pub fn jump_to_next_token(text: &str, cursor_pos: usize) -> usize {
132 let mut lexer = Lexer::new(text);
133 let tokens = lexer.tokenize_all_with_positions();
134
135 for (start, _, _) in &tokens {
137 if *start > cursor_pos {
138 return *start;
139 }
140 }
141
142 text.len()
143 }
144
145 #[must_use]
147 pub fn find_matching_bracket(text: &str, cursor_pos: usize) -> Option<usize> {
148 let chars: Vec<char> = text.chars().collect();
149 if cursor_pos >= chars.len() {
150 return None;
151 }
152
153 let ch = chars[cursor_pos];
154 let (open, close, direction) = match ch {
155 '(' => ('(', ')', 1),
156 ')' => ('(', ')', -1),
157 '[' => ('[', ']', 1),
158 ']' => ('[', ']', -1),
159 '{' => ('{', '}', 1),
160 '}' => ('{', '}', -1),
161 _ => return None,
162 };
163
164 let mut count = 1;
165 let mut pos = cursor_pos as isize;
166
167 while count > 0 {
168 pos += direction;
169 if pos < 0 || pos >= chars.len() as isize {
170 return None;
171 }
172
173 let current = chars[pos as usize];
174 if current == open {
175 count += if direction > 0 { 1 } else { -1 };
176 } else if current == close {
177 count -= if direction > 0 { 1 } else { -1 };
178 }
179 }
180
181 Some(pos as usize)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_word_boundary_backward() {
191 let text = "SELECT * FROM users WHERE id = 1";
192 assert_eq!(CursorOperations::find_word_boundary_backward(text, 14), 9); assert_eq!(CursorOperations::find_word_boundary_backward(text, 7), 0); }
195
196 #[test]
197 fn test_delete_word_backward() {
198 let text = "SELECT * FROM users";
199 let (new_text, cursor) = CursorOperations::delete_word_backward(text, 19); assert_eq!(new_text, "SELECT * FROM ");
201 assert_eq!(cursor, 14);
202 }
203
204 #[test]
205 fn test_kill_line() {
206 let text = "SELECT * FROM users WHERE id = 1";
207 let (new_text, killed) = CursorOperations::kill_line(text, 19); assert_eq!(new_text, "SELECT * FROM users");
209 assert_eq!(killed, " WHERE id = 1");
210 }
211
212 #[test]
213 fn test_matching_bracket() {
214 let text = "SELECT * FROM (SELECT id FROM users)";
215 assert_eq!(CursorOperations::find_matching_bracket(text, 14), Some(35)); assert_eq!(CursorOperations::find_matching_bracket(text, 35), Some(14));
217 }
219}