datui_lib/query/
query_prompt.rs1use crate::config::QueryMode;
5use crate::widgets::text_input::TextInput;
6use crate::{App, InputMode, InputType, QueryRun, RunOrigin, query::sql_assist};
7use polars::datatypes::DataType;
8
9pub struct QueryPrompt {
11 pub(crate) query_input: TextInput, pub(crate) sql_input: TextInput, pub find: crate::find::Find,
18 pub(crate) column_hints: bool,
20 pub(crate) input_type: Option<InputType>,
21 pub(crate) query_mode: QueryMode,
22 pub(crate) query_mode_chosen: Option<QueryMode>,
25 pub(crate) query_text_restored: bool,
28 pub(crate) sql_columns: Vec<(String, DataType)>,
31 pub(crate) sql_completion: Option<sql_assist::Cycle>,
33 pub(crate) query_running: Option<QueryRun>,
36 pub(crate) query_run_error: Option<String>,
38 pub(crate) inline_failures: u64,
41}
42
43impl QueryPrompt {
44 pub(crate) fn reset_for_dataset(&mut self) {
46 self.query_running = None;
47 }
48}
49
50impl App {
51 pub(crate) fn fail_query_run(
54 &mut self,
55 run: QueryRun,
56 message: &str,
57 conversion: Option<&crate::error_display::ConversionFailure>,
58 ) {
59 let rows = run.rows;
60 let origin = self.roll_back_query_run(run);
61 self.first_rows_settled();
62 self.status_message = None;
63 self.busy = false;
64 let mode = match origin {
68 RunOrigin::View { .. } => {
69 self.error_modal
70 .show(format!("Error applying view: {message}"));
71 self.read_after_view_rollback();
72 return;
73 }
74 RunOrigin::Query(mode) if self.query_prompt_mode() == Some(mode) => mode,
75 RunOrigin::Query(_) => {
76 self.error_modal.show(message.to_string());
77 return;
78 }
79 };
80 let sql = mode == QueryMode::Sql;
81 self.prompt.query_run_error = Some(match conversion {
82 Some(failure) if sql => failure.sql_message(rows),
83 _ => message.to_string(),
84 });
85 self.prompt.inline_failures = self.prompt.inline_failures.wrapping_add(1);
86 }
87
88 pub(crate) fn roll_back_query_run(&mut self, run: QueryRun) -> RunOrigin {
91 if let Some(state) = self.data_table_state.as_mut() {
92 state.roll_back(run.rollback);
93 }
94 self.counting.restore(run.counts);
95 if let RunOrigin::View { previous, .. } = &run.origin {
96 self.views.active_id = previous.clone();
97 }
98 run.origin
99 }
100
101 pub fn query_prompt_mode(&self) -> Option<QueryMode> {
103 (self.input_mode == InputMode::Editing && self.prompt.input_type == Some(InputType::Query))
104 .then_some(self.prompt.query_mode)
105 }
106
107 pub(crate) fn open_command_line(&mut self) {
110 let Some(state) = self.data_table_state.as_mut() else {
111 return;
112 };
113 self.input_mode = InputMode::Editing;
114 self.prompt.input_type = Some(InputType::Query);
115 self.prompt.query_run_error = None;
116 self.prompt.sql_completion = None;
117 self.prompt.query_input.set_value(state.get_active_query());
118 self.prompt
119 .sql_input
120 .set_value(state.get_active_sql_query());
121 self.prompt.query_input.select_all();
122 self.prompt.sql_input.select_all();
123 state.suppress_error_display = true;
124 self.prompt.sql_columns = state.sql_table_columns();
125 self.prompt.query_mode = self.opening_query_mode();
126 self.prompt.query_text_restored = !self.query_input_shown().is_empty();
127 self.sync_query_focus();
128 }
129
130 fn opening_query_mode(&self) -> QueryMode {
133 let active = self.data_table_state.as_ref().and_then(|state| {
134 if !state.get_active_sql_query().trim().is_empty() {
135 Some(QueryMode::Sql)
136 } else if !state.get_active_query().trim().is_empty() {
137 Some(QueryMode::Q)
138 } else {
139 None
140 }
141 });
142 active
143 .or(self.prompt.query_mode_chosen)
144 .unwrap_or(self.app_config.query.default_mode)
145 .resolve()
146 }
147
148 pub(crate) fn query_input_mut(&mut self) -> &mut TextInput {
150 match self.prompt.query_mode {
151 QueryMode::Sql => &mut self.prompt.sql_input,
152 QueryMode::Q => &mut self.prompt.query_input,
153 }
154 }
155
156 pub(crate) fn query_input_shown(&self) -> &TextInput {
158 match self.prompt.query_mode {
159 QueryMode::Sql => &self.prompt.sql_input,
160 QueryMode::Q => &self.prompt.query_input,
161 }
162 }
163
164 pub(crate) fn set_query_mode(&mut self, mode: QueryMode) {
167 self.prompt.query_mode = mode.resolve();
168 if let Some(state) = &mut self.data_table_state {
169 state.dismiss_error();
170 }
171 self.prompt.query_run_error = None;
172 self.sync_query_focus();
173 }
174
175 pub(crate) fn complete_column_name(&mut self) {
179 let sql = self.prompt.query_mode == QueryMode::Sql;
180 let columns = std::mem::take(&mut self.prompt.sql_columns);
181 let mut cycle = self.prompt.sql_completion.take();
182 let input = self.query_input_mut();
183 let line = input
184 .line_at(input.cursor_line())
185 .unwrap_or_default()
186 .to_string();
187 let value = input.value().to_string();
188 let complete = if sql {
189 sql_assist::tab
190 } else {
191 sql_assist::q_tab
192 };
193 if let Some(step) = complete(
194 &columns,
195 &line,
196 input.cursor_col(),
197 &value,
198 input.cursor(),
199 &mut cycle,
200 ) {
201 input.replace_before_cursor(step.span, &step.insert);
202 sql_assist::landed(&mut cycle, input.value(), input.cursor());
203 }
204 self.prompt.sql_completion = cycle;
205 self.prompt.sql_columns = columns;
206 }
207
208 pub(crate) fn sql_column_matches(&self) -> Vec<&(String, DataType)> {
211 let input = self.query_input_shown();
212 let line = input.line_at(input.cursor_line()).unwrap_or_default();
213 let word = match self.prompt.query_mode {
214 QueryMode::Sql => sql_assist::word_before(line, input.cursor_col()),
215 QueryMode::Q => sql_assist::q_word_before(line, input.cursor_col()),
216 }
217 .map(|w| w.text)
218 .unwrap_or_default();
219 sql_assist::matching(&self.prompt.sql_columns, &word)
220 }
221
222 pub fn query_prompt_text(&self) -> Option<&str> {
224 self.query_prompt_mode()?;
225 Some(self.query_input_shown().value())
226 }
227
228 pub fn query_prompt_error(&self) -> Option<String> {
231 if let Some(error) = &self.prompt.query_run_error {
232 return Some(error.clone());
233 }
234 let state = self.data_table_state.as_ref()?;
235 let error = state.error()?;
236 Some(if self.prompt.query_mode == QueryMode::Sql {
237 crate::error_display::sql_error_message(error, state.sql_table_rows())
238 } else {
239 crate::error_display::user_message_from_polars(error)
240 })
241 }
242
243 pub fn inline_failures(&self) -> u64 {
245 self.prompt.inline_failures
246 }
247
248 pub(crate) fn run_query(&mut self, mode: QueryMode, text: &str, status: &str) {
253 self.prompt.query_run_error = None;
254 let Some(state) = self.data_table_state.as_mut() else {
255 return;
256 };
257 let rollback = state.rollback_point();
258 let rows = state.sql_table_rows();
259 state.deferred(|s| match mode {
261 QueryMode::Sql => s.sql_query(text.to_string()),
262 QueryMode::Q => s.query(text.to_string()),
263 });
264 if state.error().is_some() {
265 return;
266 }
267 self.prompt.query_running = Some(QueryRun {
268 origin: RunOrigin::Query(mode),
269 frame: state.len_generation(),
270 rollback,
271 counts: self.counting.markers(),
272 rows,
273 });
274 if !self.spawn_async_collect(status) {
275 self.prompt.query_running = None;
277 if self.query_prompt_mode() == Some(mode) {
278 self.leave_query_prompt_after_run();
279 }
280 }
281 }
282
283 pub(crate) fn take_query_run(&mut self) -> Option<QueryRun> {
286 let run = self.prompt.query_running.take()?;
287 let frame = self.data_table_state.as_ref()?.len_generation();
288 (run.frame == frame).then_some(run)
289 }
290
291 pub(crate) fn leave_query_prompt_after_run(&mut self) {
293 self.prompt.sql_completion = None;
294 self.show_table();
295 self.prompt.sql_input.set_focused(false);
296 self.prompt.query_input.set_focused(false);
297 if let Some(state) = &mut self.data_table_state {
298 state.suppress_error_display = false;
299 }
300 }
301
302 pub(crate) fn sync_query_focus(&mut self) {
304 let mode = self.prompt.query_mode;
305 self.prompt.sql_input.set_focused(mode == QueryMode::Sql);
306 self.prompt.query_input.set_focused(mode == QueryMode::Q);
307 }
308
309 pub(crate) fn close_query_prompt(&mut self) {
311 self.prompt.query_run_error = None;
312 self.prompt.sql_completion = None;
313 self.prompt.query_input.clear();
314 self.prompt.sql_input.clear();
315 self.prompt.query_input.set_focused(false);
316 self.prompt.sql_input.set_focused(false);
317 self.show_table();
318 if let Some(state) = &mut self.data_table_state {
319 state.dismiss_error();
320 state.suppress_error_display = false;
321 }
322 }
323}