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