1use crate::recursive_parser::{Lexer, Token};
2
3pub struct TextNavigator;
6
7impl TextNavigator {
8 pub fn get_cursor_token_position(query: &str, cursor_pos: usize) -> (usize, usize) {
10 if query.is_empty() {
11 return (0, 0);
12 }
13
14 let mut lexer = Lexer::new(query);
16 let tokens = lexer.tokenize_all_with_positions();
17
18 if tokens.is_empty() {
19 return (0, 0);
20 }
21
22 if cursor_pos == 0 {
24 return (0, tokens.len());
25 }
26
27 let mut current_token = 0;
29 for (i, (start, end, _)) in tokens.iter().enumerate() {
30 if cursor_pos >= *start && cursor_pos <= *end {
31 current_token = i + 1;
32 break;
33 } else if cursor_pos < *start {
34 current_token = i;
36 break;
37 }
38 }
39
40 if current_token == 0 && cursor_pos > 0 {
42 current_token = tokens.len();
43 }
44
45 (current_token, tokens.len())
46 }
47
48 pub fn get_token_at_cursor(query: &str, cursor_pos: usize) -> Option<String> {
50 if query.is_empty() {
51 return None;
52 }
53
54 let mut lexer = Lexer::new(query);
56 let tokens = lexer.tokenize_all_with_positions();
57
58 for (start, end, token) in &tokens {
60 if cursor_pos >= *start && cursor_pos <= *end {
61 let token_str = Self::format_token(token);
63 return Some(token_str.to_string());
64 }
65 }
66
67 None
68 }
69
70 pub fn calculate_prev_token_position(query: &str, cursor_pos: usize) -> Option<usize> {
72 if cursor_pos == 0 {
73 return None;
74 }
75
76 let mut lexer = Lexer::new(query);
77 let tokens = lexer.tokenize_all_with_positions();
78
79 let mut in_token = false;
81 let mut current_token_start = 0;
82 for (start, end, _) in &tokens {
83 if cursor_pos > *start && cursor_pos <= *end {
84 in_token = true;
85 current_token_start = *start;
86 break;
87 }
88 }
89
90 let target_pos = if in_token && cursor_pos > current_token_start {
92 current_token_start
94 } else {
95 let mut prev_start = 0;
97 for (start, _, _) in tokens.iter().rev() {
98 if *start < cursor_pos {
99 prev_start = *start;
100 break;
101 }
102 }
103 prev_start
104 };
105
106 if target_pos < cursor_pos {
107 Some(target_pos)
108 } else {
109 None
110 }
111 }
112
113 pub fn calculate_next_token_position(query: &str, cursor_pos: usize) -> Option<usize> {
115 let query_len = query.len();
116 if cursor_pos >= query_len {
117 return None;
118 }
119
120 let mut lexer = Lexer::new(query);
121 let tokens = lexer.tokenize_all_with_positions();
122
123 let mut in_token = false;
125 let mut current_token_end = query_len;
126 for (start, end, _) in &tokens {
127 if cursor_pos >= *start && cursor_pos < *end {
128 in_token = true;
129 current_token_end = *end;
130 break;
131 }
132 }
133
134 let target_pos = if in_token && cursor_pos < current_token_end {
136 let mut next_start = query_len;
138 for (start, _, _) in &tokens {
139 if *start > current_token_end {
140 next_start = *start;
141 break;
142 }
143 }
144 next_start
145 } else {
146 let mut next_start = query_len;
148 for (start, _, _) in &tokens {
149 if *start > cursor_pos {
150 next_start = *start;
151 break;
152 }
153 }
154 next_start
155 };
156
157 if target_pos > cursor_pos && target_pos <= query_len {
158 Some(target_pos)
159 } else {
160 None
161 }
162 }
163
164 fn format_token(token: &Token) -> &str {
166 match token {
167 Token::Select => "SELECT",
168 Token::From => "FROM",
169 Token::Where => "WHERE",
170 Token::GroupBy => "GROUP BY",
171 Token::OrderBy => "ORDER BY",
172 Token::Having => "HAVING",
173 Token::As => "AS",
174 Token::Asc => "ASC",
175 Token::Desc => "DESC",
176 Token::And => "AND",
177 Token::Or => "OR",
178 Token::In => "IN",
179 Token::DateTime => "DateTime",
180 Token::Case => "CASE",
181 Token::When => "WHEN",
182 Token::Then => "THEN",
183 Token::Else => "ELSE",
184 Token::End => "END",
185 Token::Distinct => "DISTINCT",
186 Token::Identifier(s) => s,
187 Token::QuotedIdentifier(s) => s,
188 Token::StringLiteral(s) => s,
189 Token::NumberLiteral(s) => s,
190 Token::Star => "*",
191 Token::Comma => ",",
192 Token::Dot => ".",
193 Token::LeftParen => "(",
194 Token::RightParen => ")",
195 Token::Equal => "=",
196 Token::NotEqual => "!=",
197 Token::LessThan => "<",
198 Token::LessThanOrEqual => "<=",
199 Token::GreaterThan => ">",
200 Token::GreaterThanOrEqual => ">=",
201 Token::Like => "LIKE",
202 Token::Not => "NOT",
203 Token::Is => "IS",
204 Token::Null => "NULL",
205 Token::Between => "BETWEEN",
206 Token::Limit => "LIMIT",
207 Token::Offset => "OFFSET",
208 Token::Plus => "+",
209 Token::Minus => "-",
210 Token::Divide => "/",
211 Token::Eof => "EOF",
212 }
213 }
214}
215
216pub struct TextEditor;
218
219impl TextEditor {
220 pub fn kill_line_backward(text: &str, cursor_pos: usize) -> Option<(String, String)> {
223 if cursor_pos == 0 {
224 return None;
225 }
226
227 let killed_text = text.chars().take(cursor_pos).collect::<String>();
228 let remaining_text = text.chars().skip(cursor_pos).collect::<String>();
229
230 Some((killed_text, remaining_text))
231 }
232
233 pub fn kill_line_forward(text: &str, cursor_pos: usize) -> Option<(String, String)> {
236 if cursor_pos >= text.len() {
237 return None;
238 }
239
240 let remaining_text = text.chars().take(cursor_pos).collect::<String>();
241 let killed_text = text.chars().skip(cursor_pos).collect::<String>();
242
243 Some((killed_text, remaining_text))
244 }
245
246 pub fn delete_word_backward(text: &str, cursor_pos: usize) -> Option<(String, String, usize)> {
249 if cursor_pos == 0 {
250 return None;
251 }
252
253 let before_cursor = &text[..cursor_pos];
254 let after_cursor = &text[cursor_pos..];
255
256 let mut word_start = before_cursor.len();
258 let mut chars = before_cursor.chars().rev().peekable();
259
260 while let Some(&ch) = chars.peek() {
262 if ch.is_whitespace() {
263 word_start -= ch.len_utf8();
264 chars.next();
265 } else {
266 break;
267 }
268 }
269
270 while let Some(&ch) = chars.peek() {
272 if !ch.is_alphanumeric() && ch != '_' {
273 break;
274 }
275 word_start -= ch.len_utf8();
276 chars.next();
277 }
278
279 while let Some(&ch) = chars.peek() {
281 if ch.is_whitespace() {
282 word_start -= ch.len_utf8();
283 chars.next();
284 } else {
285 break;
286 }
287 }
288
289 let deleted_text = text[word_start..cursor_pos].to_string();
290 let remaining_text = format!("{}{}", &text[..word_start], after_cursor);
291
292 Some((deleted_text, remaining_text, word_start))
293 }
294
295 pub fn delete_word_forward(text: &str, cursor_pos: usize) -> Option<(String, String)> {
298 if cursor_pos >= text.len() {
299 return None;
300 }
301
302 let before_cursor = &text[..cursor_pos];
303 let after_cursor = &text[cursor_pos..];
304
305 let mut chars = after_cursor.chars();
307 let mut word_end = 0;
308
309 while let Some(ch) = chars.next() {
311 word_end += ch.len_utf8();
312 if ch.is_alphanumeric() || ch == '_' {
313 while let Some(ch) = chars.next() {
315 if !ch.is_alphanumeric() && ch != '_' {
316 break;
317 }
318 word_end += ch.len_utf8();
319 }
320 break;
321 }
322 }
323
324 let deleted_text = text[cursor_pos..cursor_pos + word_end].to_string();
325 let remaining_text = format!("{}{}", before_cursor, &after_cursor[word_end..]);
326
327 Some((deleted_text, remaining_text))
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_cursor_token_position() {
337 let query = "SELECT * FROM users WHERE id = 1";
338
339 assert_eq!(TextNavigator::get_cursor_token_position(query, 0), (0, 8));
341
342 assert_eq!(TextNavigator::get_cursor_token_position(query, 3), (1, 8));
344
345 assert_eq!(TextNavigator::get_cursor_token_position(query, 7), (2, 8));
347 }
348
349 #[test]
350 fn test_kill_line_backward() {
351 let text = "SELECT * FROM users";
352
353 let result = TextEditor::kill_line_backward(text, 8);
355 assert_eq!(
356 result,
357 Some(("SELECT *".to_string(), " FROM users".to_string()))
358 );
359
360 let result = TextEditor::kill_line_backward(text, 0);
362 assert_eq!(result, None);
363 }
364
365 #[test]
366 fn test_delete_word_backward() {
367 let text = "SELECT * FROM users";
368
369 let result = TextEditor::delete_word_backward(text, 13);
371 assert_eq!(
372 result,
373 Some((" FROM".to_string(), "SELECT * users".to_string(), 8))
374 );
375 }
376}