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 { applying, .. } => {
69 self.view_failed(&applying, message);
70 self.read_after_view_rollback();
71 return;
72 }
73 RunOrigin::Query(mode) if self.query_prompt_mode() == Some(mode) => mode,
74 RunOrigin::Query(_) => {
75 self.say_read_failed(message);
76 return;
77 }
78 };
79 let sql = mode == QueryMode::Sql;
82 self.prompt.query_run_error = Some(match conversion {
83 Some(failure) if sql => failure.sql_message(rows),
84 _ => message.to_string(),
85 });
86 self.prompt.inline_failures = self.prompt.inline_failures.wrapping_add(1);
87 }
88
89 pub(crate) fn roll_back_query_run(&mut self, run: QueryRun) -> RunOrigin {
92 if let Some(state) = self.data_table_state.as_mut() {
93 state.roll_back(run.rollback);
94 }
95 self.counting.restore(run.counts);
96 if let RunOrigin::View { previous, .. } = &run.origin {
97 self.views.active_id = previous.clone();
98 }
99 run.origin
100 }
101
102 pub fn query_prompt_mode(&self) -> Option<QueryMode> {
104 (self.input_mode == InputMode::Editing && self.prompt.input_type == Some(InputType::Query))
105 .then_some(self.prompt.query_mode)
106 }
107
108 pub(crate) fn open_command_line(&mut self) {
111 let Some(state) = self.data_table_state.as_mut() else {
112 return;
113 };
114 self.input_mode = InputMode::Editing;
115 self.prompt.input_type = Some(InputType::Query);
116 self.prompt.query_run_error = None;
117 self.prompt.sql_completion = None;
118 self.prompt.query_input.set_value(state.get_active_query());
119 self.prompt
120 .sql_input
121 .set_value(state.get_active_sql_query());
122 self.prompt.query_input.select_all();
123 self.prompt.sql_input.select_all();
124 state.suppress_error_display = true;
125 self.prompt.sql_columns = state.sql_table_columns();
126 self.prompt.query_mode = self.opening_query_mode();
127 self.prompt.query_text_restored = !self.query_input_shown().is_empty();
128 self.sync_query_focus();
129 }
130
131 fn opening_query_mode(&self) -> QueryMode {
134 let active = self.data_table_state.as_ref().and_then(|state| {
135 if !state.get_active_sql_query().trim().is_empty() {
136 Some(QueryMode::Sql)
137 } else if !state.get_active_query().trim().is_empty() {
138 Some(QueryMode::Q)
139 } else {
140 None
141 }
142 });
143 active
144 .or(self.prompt.query_mode_chosen)
145 .unwrap_or(self.app_config.query.default_mode)
146 .resolve()
147 }
148
149 pub(crate) fn query_input_mut(&mut self) -> &mut TextInput {
151 match self.prompt.query_mode {
152 QueryMode::Sql => &mut self.prompt.sql_input,
153 QueryMode::Q => &mut self.prompt.query_input,
154 }
155 }
156
157 pub(crate) fn query_input_shown(&self) -> &TextInput {
159 match self.prompt.query_mode {
160 QueryMode::Sql => &self.prompt.sql_input,
161 QueryMode::Q => &self.prompt.query_input,
162 }
163 }
164
165 pub(crate) fn set_query_mode(&mut self, mode: QueryMode) {
168 self.prompt.query_mode = mode.resolve();
169 if let Some(state) = &mut self.data_table_state {
170 state.dismiss_error();
171 }
172 self.prompt.query_run_error = None;
173 self.sync_query_focus();
174 }
175
176 pub(crate) fn complete_column_name(&mut self) {
180 let sql = self.prompt.query_mode == QueryMode::Sql;
181 let columns = std::mem::take(&mut self.prompt.sql_columns);
182 let mut cycle = self.prompt.sql_completion.take();
183 let input = self.query_input_mut();
184 let line = input
185 .line_at(input.cursor_line())
186 .unwrap_or_default()
187 .to_string();
188 let value = input.value().to_string();
189 let complete = if sql {
190 sql_assist::tab
191 } else {
192 sql_assist::q_tab
193 };
194 if let Some(step) = complete(
195 &columns,
196 &line,
197 input.cursor_col(),
198 &value,
199 input.cursor(),
200 &mut cycle,
201 ) {
202 input.replace_before_cursor(step.span, &step.insert);
203 sql_assist::landed(&mut cycle, input.value(), input.cursor());
204 }
205 self.prompt.sql_completion = cycle;
206 self.prompt.sql_columns = columns;
207 }
208
209 pub(crate) fn sql_column_matches(&self) -> Vec<&(String, DataType)> {
212 let input = self.query_input_shown();
213 let line = input.line_at(input.cursor_line()).unwrap_or_default();
214 let word = match self.prompt.query_mode {
215 QueryMode::Sql => sql_assist::word_before(line, input.cursor_col()),
216 QueryMode::Q => sql_assist::q_word_before(line, input.cursor_col()),
217 }
218 .map(|w| w.text)
219 .unwrap_or_default();
220 sql_assist::matching(&self.prompt.sql_columns, &word)
221 }
222
223 pub fn query_prompt_text(&self) -> Option<&str> {
225 self.query_prompt_mode()?;
226 Some(self.query_input_shown().value())
227 }
228
229 pub fn query_prompt_error(&self) -> Option<String> {
232 if let Some(error) = &self.prompt.query_run_error {
233 return Some(error.clone());
234 }
235 let state = self.data_table_state.as_ref()?;
236 let error = state.error()?;
237 Some(if self.prompt.query_mode == QueryMode::Sql {
238 crate::error_display::sql_error_message(error, state.sql_table_rows())
239 } else {
240 crate::error_display::user_message_from_polars(error)
241 })
242 }
243
244 pub fn inline_failures(&self) -> u64 {
246 self.prompt.inline_failures
247 }
248
249 pub(crate) fn run_query(&mut self, mode: QueryMode, text: &str, status: &str) {
254 self.prompt.query_run_error = None;
255 let Some(state) = self.data_table_state.as_mut() else {
256 return;
257 };
258 let rollback = state.rollback_point();
259 let rows = state.sql_table_rows();
260 state.deferred(|s| match mode {
262 QueryMode::Sql => s.sql_query(text.to_string()),
263 QueryMode::Q => s.query(text.to_string()),
264 });
265 if state.error().is_some() {
266 return;
267 }
268 self.prompt.query_running = Some(QueryRun {
269 origin: RunOrigin::Query(mode),
270 frame: state.len_generation(),
271 rollback,
272 counts: self.counting.markers(),
273 rows,
274 });
275 if !self.spawn_async_collect(status) {
276 self.prompt.query_running = None;
278 if self.query_prompt_mode() == Some(mode) {
279 self.leave_query_prompt_after_run();
280 }
281 }
282 }
283
284 pub(crate) fn query_run(&self) -> Option<&QueryRun> {
286 let run = self.prompt.query_running.as_ref()?;
287 let frame = self.data_table_state.as_ref()?.len_generation();
288 (run.frame == frame).then_some(run)
289 }
290
291 pub(crate) fn take_query_run(&mut self) -> Option<QueryRun> {
294 let run = self.prompt.query_running.take()?;
295 let frame = self.data_table_state.as_ref()?.len_generation();
296 (run.frame == frame).then_some(run)
297 }
298
299 pub(crate) fn leave_query_prompt_after_run(&mut self) {
301 self.prompt.sql_completion = None;
302 self.show_table();
303 self.prompt.sql_input.set_focused(false);
304 self.prompt.query_input.set_focused(false);
305 if let Some(state) = &mut self.data_table_state {
306 state.suppress_error_display = false;
307 }
308 }
309
310 pub(crate) fn sync_query_focus(&mut self) {
312 let mode = self.prompt.query_mode;
313 self.prompt.sql_input.set_focused(mode == QueryMode::Sql);
314 self.prompt.query_input.set_focused(mode == QueryMode::Q);
315 }
316
317 pub(crate) fn close_query_prompt(&mut self) {
319 self.prompt.query_run_error = None;
320 self.prompt.sql_completion = None;
321 self.prompt.query_input.clear();
322 self.prompt.sql_input.clear();
323 self.prompt.query_input.set_focused(false);
324 self.prompt.sql_input.set_focused(false);
325 self.show_table();
326 if let Some(state) = &mut self.data_table_state {
327 state.dismiss_error();
328 state.suppress_error_display = false;
329 }
330 }
331}