sql_cli/ui/operations/
simple_operations.rs1use crate::buffer::{BufferAPI, BufferManager};
7use crate::text_navigation::TextNavigator;
8
9pub struct TextNavigationContext<'a> {
11 pub query: &'a str,
12 pub cursor_pos: usize,
13}
14
15pub struct UndoContext<'a> {
17 pub buffer_manager: &'a mut BufferManager,
18}
19
20#[must_use]
23pub fn get_cursor_token_position(ctx: &TextNavigationContext) -> (usize, usize) {
24 TextNavigator::get_cursor_token_position(ctx.query, ctx.cursor_pos)
25}
26
27#[must_use]
30pub fn get_token_at_cursor(ctx: &TextNavigationContext) -> Option<String> {
31 TextNavigator::get_token_at_cursor(ctx.query, ctx.cursor_pos)
32}
33
34#[derive(Debug, PartialEq)]
36pub enum UndoResult {
37 Success,
39 NothingToUndo,
41 NoBuffer,
43}
44
45impl UndoResult {
46 #[must_use]
48 pub fn status_message(&self) -> &'static str {
49 match self {
50 UndoResult::Success => "Undo performed",
51 UndoResult::NothingToUndo => "Nothing to undo",
52 UndoResult::NoBuffer => "No buffer available for undo",
53 }
54 }
55}
56
57pub fn perform_undo(ctx: &mut UndoContext) -> UndoResult {
59 if let Some(buffer) = ctx.buffer_manager.current_mut() {
60 if buffer.perform_undo() {
61 UndoResult::Success
62 } else {
63 UndoResult::NothingToUndo
64 }
65 } else {
66 UndoResult::NoBuffer
67 }
68}
69
70#[must_use]
73pub fn check_parser_error(query: &str) -> Option<String> {
74 let mut paren_depth = 0;
76 let mut in_string = false;
77 let mut escape_next = false;
78
79 for ch in query.chars() {
80 if escape_next {
81 escape_next = false;
82 continue;
83 }
84
85 match ch {
86 '\\' if in_string => escape_next = true,
87 '\'' => in_string = !in_string,
88 '(' if !in_string => paren_depth += 1,
89 ')' if !in_string => {
90 paren_depth -= 1;
91 if paren_depth < 0 {
92 return Some("Extra )".to_string());
93 }
94 }
95 _ => {}
96 }
97 }
98
99 if paren_depth > 0 {
100 return Some(format!("Missing {paren_depth} )"));
101 }
102
103 if in_string {
105 return Some("Unclosed string".to_string());
106 }
107
108 None
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_get_cursor_token_position() {
117 let ctx = TextNavigationContext {
118 query: "SELECT name FROM users",
119 cursor_pos: 2, };
121
122 let (start, end) = get_cursor_token_position(&ctx);
123 assert!(start <= ctx.cursor_pos);
126 assert!(end >= ctx.cursor_pos);
127 }
128
129 #[test]
130 fn test_get_token_at_cursor() {
131 let ctx = TextNavigationContext {
132 query: "SELECT name FROM users",
133 cursor_pos: 2, };
135
136 let token = get_token_at_cursor(&ctx);
137 assert!(token.is_some() || token.is_none()); }
141
142 #[test]
143 fn test_get_token_at_cursor_empty_query() {
144 let ctx = TextNavigationContext {
145 query: "",
146 cursor_pos: 0,
147 };
148
149 let token = get_token_at_cursor(&ctx);
150 assert!(token.is_none() || token.is_some()); }
153
154 #[test]
155 fn test_undo_result_status_messages() {
156 assert_eq!(UndoResult::Success.status_message(), "Undo performed");
157 assert_eq!(
158 UndoResult::NothingToUndo.status_message(),
159 "Nothing to undo"
160 );
161 assert_eq!(
162 UndoResult::NoBuffer.status_message(),
163 "No buffer available for undo"
164 );
165 }
166
167 #[test]
168 fn test_check_parser_error_valid_queries() {
169 assert_eq!(check_parser_error("SELECT * FROM users"), None);
170 assert_eq!(
171 check_parser_error("SELECT name FROM users WHERE id = 1"),
172 None
173 );
174 assert_eq!(
175 check_parser_error("SELECT (column1 + column2) FROM table"),
176 None
177 );
178 assert_eq!(check_parser_error("SELECT 'hello world' FROM dual"), None);
179 }
180
181 #[test]
182 fn test_check_parser_error_mismatched_parens() {
183 assert_eq!(
184 check_parser_error("SELECT (column FROM table"),
185 Some("Missing 1 )".to_string())
186 );
187 assert_eq!(
188 check_parser_error("SELECT ((column FROM table"),
189 Some("Missing 2 )".to_string())
190 );
191 assert_eq!(
192 check_parser_error("SELECT column) FROM table"),
193 Some("Extra )".to_string())
194 );
195 }
196
197 #[test]
198 fn test_check_parser_error_unclosed_string() {
199 assert_eq!(
200 check_parser_error("SELECT 'unclosed FROM table"),
201 Some("Unclosed string".to_string())
202 );
203 assert_eq!(
204 check_parser_error("SELECT name FROM users WHERE name = 'test"),
205 Some("Unclosed string".to_string())
206 );
207 }
208
209 #[test]
210 fn test_check_parser_error_escaped_quotes() {
211 assert_eq!(
213 check_parser_error("SELECT 'it\\'s a test' FROM table"),
214 None
215 );
216 }
217
218 #[test]
219 fn test_check_parser_error_parens_in_strings() {
220 assert_eq!(
222 check_parser_error("SELECT 'text with (parens)' FROM table"),
223 None
224 );
225 assert_eq!(
226 check_parser_error("SELECT 'text with (unclosed FROM table"),
227 Some("Unclosed string".to_string())
228 );
229 }
230
231 #[test]
232 fn test_check_parser_error_empty_query() {
233 assert_eq!(check_parser_error(""), None);
234 }
235
236 }