datafusion_dft/tui/state/tabs/
sql.rs1use core::cell::RefCell;
19
20use color_eyre::Result;
21use datafusion::arrow::array::RecordBatch;
22use datafusion::sql::sqlparser::keywords;
23use log::debug;
24use ratatui::crossterm::event::KeyEvent;
25use ratatui::style::palette::tailwind;
26use ratatui::style::{Modifier, Style};
27use ratatui::widgets::TableState;
28use tokio::task::JoinHandle;
29use tui_textarea::TextArea;
30
31use crate::config::AppConfig;
32use crate::tui::ExecutionError;
33
34pub fn get_keywords() -> Vec<String> {
35 keywords::ALL_KEYWORDS
36 .iter()
37 .map(|k| k.to_string())
38 .collect()
39}
40
41pub fn keyword_regex() -> String {
42 format!(
43 "(?i)(^|[^a-zA-Z0-9\'\"`._]*?)({})($|[^a-zA-Z0-9\'\"`._]*)",
44 get_keywords().join("|")
45 )
46}
47
48pub fn keyword_style() -> Style {
49 Style::default()
50 .bg(tailwind::BLACK)
51 .fg(tailwind::YELLOW.c100)
52 .add_modifier(Modifier::BOLD)
53}
54
55#[derive(Debug, Default, PartialEq)]
56pub enum SQLTabMode {
57 #[default]
58 Normal,
59 DDL,
60}
61
62#[derive(Debug, Default)]
63pub struct SQLTabState<'app> {
64 editor: TextArea<'app>,
65 editor_editable: bool,
66 ddl_error: bool,
67 ddl_editor: TextArea<'app>,
68 ddl_editor_editable: bool,
69 query_results_state: Option<RefCell<TableState>>,
70 result_batches: Option<Vec<RecordBatch>>,
71 current_page: Option<usize>,
72 execution_error: Option<ExecutionError>,
73 execution_task: Option<JoinHandle<Result<()>>>,
74 mode: SQLTabMode,
75}
76
77impl SQLTabState<'_> {
78 pub fn new(config: &AppConfig) -> Self {
79 let empty_text = vec!["Enter a query here.".to_string()];
80 let mut textarea = TextArea::new(empty_text);
82 textarea.set_style(Style::default().fg(tailwind::WHITE));
83
84 let ddl_empty_text = vec!["Write your DDL here.".to_string()];
85 let mut ddl_textarea = TextArea::new(ddl_empty_text);
86 ddl_textarea.set_style(Style::default().fg(tailwind::WHITE));
87 if config.editor.experimental_syntax_highlighting {
88 textarea.set_search_pattern(keyword_regex()).unwrap();
89 textarea.set_search_style(keyword_style());
90 ddl_textarea.set_search_pattern(keyword_regex()).unwrap();
91 ddl_textarea.set_search_style(keyword_style());
92 };
93 Self {
94 editor: textarea,
95 editor_editable: false,
96 ddl_error: false,
97 ddl_editor: ddl_textarea,
98 ddl_editor_editable: false,
99 query_results_state: None,
100 result_batches: None,
101 current_page: None,
102 execution_error: None,
103 execution_task: None,
104 mode: SQLTabMode::default(),
105 }
106 }
107
108 pub fn query_results_state(&self) -> &Option<RefCell<TableState>> {
109 &self.query_results_state
110 }
111
112 pub fn refresh_query_results_state(&mut self) {
113 self.query_results_state = Some(RefCell::new(TableState::default()));
114 }
115
116 pub fn reset_execution_results(&mut self) {
117 self.result_batches = None;
118 self.current_page = None;
119 self.execution_error = None;
120 self.refresh_query_results_state();
121 }
122
123 pub fn editor(&self) -> TextArea {
124 self.editor.clone()
127 }
128
129 pub fn ddl_error(&self) -> bool {
130 self.ddl_error
131 }
132
133 pub fn set_ddl_error(&mut self, error: bool) {
134 self.ddl_error = error;
135 }
136
137 pub fn ddl_editor(&self) -> TextArea {
138 self.ddl_editor.clone()
139 }
140
141 pub fn active_editor_cloned(&self) -> TextArea {
142 match self.mode {
143 SQLTabMode::Normal => self.editor.clone(),
144 SQLTabMode::DDL => self.ddl_editor.clone(),
145 }
146 }
147
148 pub fn clear_placeholder(&mut self) {
149 let default = "Enter a query here.";
150 let lines = self.editor.lines();
151 let content = lines.join("");
152 if content == default {
153 self.editor
154 .move_cursor(tui_textarea::CursorMove::Jump(0, 0));
155 self.editor.delete_str(default.len());
156 }
157 }
158
159 pub fn clear_editor(&mut self, config: &AppConfig) {
160 let mut textarea = TextArea::new(vec!["".to_string()]);
161 textarea.set_style(Style::default().fg(tailwind::WHITE));
162 if config.editor.experimental_syntax_highlighting {
163 textarea.set_search_pattern(keyword_regex()).unwrap();
164 textarea.set_search_style(keyword_style());
165 };
166 self.editor = textarea;
167 }
168
169 pub fn update_editor_content(&mut self, key: KeyEvent) {
170 match self.mode {
171 SQLTabMode::Normal => self.editor.input(key),
172 SQLTabMode::DDL => self.ddl_editor.input(key),
173 };
174 }
175
176 pub fn add_ddl_to_editor(&mut self, ddl: String) {
177 debug!("Adding DDL to editor: {}", ddl);
178 self.ddl_editor.delete_line_by_end();
179 self.ddl_editor.set_yank_text(ddl);
180 self.ddl_editor.paste();
181 }
182
183 pub fn edit(&mut self) {
184 match self.mode {
185 SQLTabMode::Normal => self.editor_editable = true,
186 SQLTabMode::DDL => self.ddl_editor_editable = true,
187 };
188 }
189
190 pub fn exit_edit(&mut self) {
191 match self.mode {
192 SQLTabMode::Normal => self.editor_editable = false,
193 SQLTabMode::DDL => self.ddl_editor_editable = false,
194 };
195 }
196
197 pub fn editor_editable(&self) -> bool {
198 match self.mode {
199 SQLTabMode::Normal => self.editor_editable,
200 SQLTabMode::DDL => self.ddl_editor_editable,
201 }
202 }
203
204 pub fn editable(&self) -> bool {
205 self.editor_editable || self.ddl_editor_editable
206 }
207
208 pub fn next_word(&mut self) {
210 match self.mode {
211 SQLTabMode::Normal => self
212 .editor
213 .move_cursor(tui_textarea::CursorMove::WordForward),
214 SQLTabMode::DDL => self
215 .ddl_editor
216 .move_cursor(tui_textarea::CursorMove::WordForward),
217 }
218 }
219
220 pub fn previous_word(&mut self) {
222 match self.mode {
223 SQLTabMode::Normal => self.editor.move_cursor(tui_textarea::CursorMove::WordBack),
224 SQLTabMode::DDL => self
225 .ddl_editor
226 .move_cursor(tui_textarea::CursorMove::WordBack),
227 }
228 }
229
230 pub fn delete_word(&mut self) {
231 match self.mode {
232 SQLTabMode::Normal => self.editor.delete_word(),
233 SQLTabMode::DDL => self.ddl_editor.delete_word(),
234 };
235 }
236
237 pub fn add_batch(&mut self, batch: RecordBatch) {
238 if let Some(batches) = self.result_batches.as_mut() {
239 batches.push(batch);
240 } else {
241 self.result_batches = Some(vec![batch]);
242 }
243 }
244
245 pub fn current_batch(&self) -> Option<&RecordBatch> {
246 match (self.current_page, self.result_batches.as_ref()) {
247 (Some(page), Some(batches)) => batches.get(page),
248 _ => None,
249 }
250 }
251
252 pub fn batches_count(&self) -> usize {
253 if let Some(batches) = &self.result_batches {
254 batches.len()
255 } else {
256 0
257 }
258 }
259
260 pub fn execution_error(&self) -> &Option<ExecutionError> {
261 &self.execution_error
262 }
263
264 pub fn set_execution_error(&mut self, error: ExecutionError) {
265 self.execution_error = Some(error);
266 }
267
268 pub fn current_page(&self) -> Option<usize> {
269 self.current_page
270 }
271
272 pub fn next_page(&mut self) {
273 if let Some(page) = self.current_page {
274 self.current_page = Some(page + 1);
275 } else {
276 self.current_page = Some(0);
277 }
278 }
279
280 pub fn previous_page(&mut self) {
281 if let Some(page) = self.current_page {
282 if page > 0 {
283 self.current_page = Some(page - 1);
284 }
285 }
286 }
287
288 pub fn execution_task(&mut self) -> &mut Option<JoinHandle<Result<()>>> {
289 &mut self.execution_task
290 }
291
292 pub fn set_execution_task(&mut self, task: JoinHandle<Result<()>>) {
293 self.execution_task = Some(task);
294 }
295
296 pub fn mode(&self) -> &SQLTabMode {
297 &self.mode
298 }
299
300 pub fn set_mode(&mut self, mode: SQLTabMode) {
301 self.mode = mode
302 }
303
304 pub fn sql(&self) -> String {
307 match self.mode {
308 SQLTabMode::Normal => {
309 if let Some(((start_row, start_col), (end_row, end_col))) =
310 self.editor.selection_range()
311 {
312 if start_row == end_row {
313 let line = &self.editor.lines()[start_row];
314 line.chars()
315 .skip(start_col)
316 .take(end_col - start_col)
317 .collect()
318 } else {
319 let lines: Vec<String> = self
320 .editor
321 .lines()
322 .iter()
323 .enumerate()
324 .map(|(i, line)| {
325 let selected_chars: Vec<char> = if i == start_row {
326 line.chars().skip(start_col).collect()
327 } else if i == end_row {
328 line.chars().take(end_col).collect()
329 } else {
330 line.chars().collect()
331 };
332 selected_chars.into_iter().collect()
333 })
334 .collect();
335 lines.join("\n")
336 }
337 } else {
338 self.editor.lines().join("\n")
339 }
340 }
341 SQLTabMode::DDL => self.ddl_editor.lines().join("\n"),
342 }
343 }
344}