use crate::config::QueryMode;
use crate::widgets::text_input::TextInput;
use crate::{App, InputMode, InputType, QueryRun, RunOrigin, query::sql_assist};
use polars::datatypes::DataType;
pub struct QueryPrompt {
pub(crate) query_input: TextInput, pub(crate) sql_input: TextInput, pub find: crate::find::Find,
pub(crate) column_hints: bool,
pub(crate) input_type: Option<InputType>,
pub(crate) query_mode: QueryMode,
pub(crate) query_mode_chosen: Option<QueryMode>,
pub(crate) query_text_restored: bool,
pub(crate) sql_columns: Vec<(String, DataType)>,
pub(crate) sql_completion: Option<sql_assist::Cycle>,
pub(crate) query_running: Option<QueryRun>,
pub(crate) query_run_error: Option<String>,
pub(crate) inline_failures: u64,
}
impl QueryPrompt {
pub(crate) fn reset_for_dataset(&mut self) {
self.query_running = None;
}
}
impl App {
pub(crate) fn fail_query_run(
&mut self,
run: QueryRun,
message: &str,
conversion: Option<&crate::error_display::ConversionFailure>,
) {
let rows = run.rows;
let origin = self.roll_back_query_run(run);
self.first_rows_settled();
self.status_message = None;
self.busy = false;
let mode = match origin {
RunOrigin::View { applying, .. } => {
self.view_failed(&applying, message);
self.read_after_view_rollback();
return;
}
RunOrigin::Query(mode) if self.query_prompt_mode() == Some(mode) => mode,
RunOrigin::Query(_) => {
self.say_read_failed(message);
return;
}
};
let sql = mode == QueryMode::Sql;
self.prompt.query_run_error = Some(match conversion {
Some(failure) if sql => failure.sql_message(rows),
_ => message.to_string(),
});
self.prompt.inline_failures = self.prompt.inline_failures.wrapping_add(1);
}
pub(crate) fn roll_back_query_run(&mut self, run: QueryRun) -> RunOrigin {
if let Some(state) = self.data_table_state.as_mut() {
state.roll_back(run.rollback);
}
self.counting.restore(run.counts);
if let RunOrigin::View { previous, .. } = &run.origin {
self.views.active_id = previous.clone();
}
run.origin
}
pub fn query_prompt_mode(&self) -> Option<QueryMode> {
(self.input_mode == InputMode::Editing && self.prompt.input_type == Some(InputType::Query))
.then_some(self.prompt.query_mode)
}
pub(crate) fn open_command_line(&mut self) {
let Some(state) = self.data_table_state.as_mut() else {
return;
};
self.input_mode = InputMode::Editing;
self.prompt.input_type = Some(InputType::Query);
self.prompt.query_run_error = None;
self.prompt.sql_completion = None;
self.prompt.query_input.set_value(state.get_active_query());
self.prompt
.sql_input
.set_value(state.get_active_sql_query());
self.prompt.query_input.select_all();
self.prompt.sql_input.select_all();
state.suppress_error_display = true;
self.prompt.sql_columns = state.sql_table_columns();
self.prompt.query_mode = self.opening_query_mode();
self.prompt.query_text_restored = !self.query_input_shown().is_empty();
self.sync_query_focus();
}
fn opening_query_mode(&self) -> QueryMode {
let active = self.data_table_state.as_ref().and_then(|state| {
if !state.get_active_sql_query().trim().is_empty() {
Some(QueryMode::Sql)
} else if !state.get_active_query().trim().is_empty() {
Some(QueryMode::Q)
} else {
None
}
});
active
.or(self.prompt.query_mode_chosen)
.unwrap_or(self.app_config.query.default_mode)
.resolve()
}
pub(crate) fn query_input_mut(&mut self) -> &mut TextInput {
match self.prompt.query_mode {
QueryMode::Sql => &mut self.prompt.sql_input,
QueryMode::Q => &mut self.prompt.query_input,
}
}
pub(crate) fn query_input_shown(&self) -> &TextInput {
match self.prompt.query_mode {
QueryMode::Sql => &self.prompt.sql_input,
QueryMode::Q => &self.prompt.query_input,
}
}
pub(crate) fn set_query_mode(&mut self, mode: QueryMode) {
self.prompt.query_mode = mode.resolve();
if let Some(state) = &mut self.data_table_state {
state.dismiss_error();
}
self.prompt.query_run_error = None;
self.sync_query_focus();
}
pub(crate) fn complete_column_name(&mut self) {
let sql = self.prompt.query_mode == QueryMode::Sql;
let columns = std::mem::take(&mut self.prompt.sql_columns);
let mut cycle = self.prompt.sql_completion.take();
let input = self.query_input_mut();
let line = input
.line_at(input.cursor_line())
.unwrap_or_default()
.to_string();
let value = input.value().to_string();
let complete = if sql {
sql_assist::tab
} else {
sql_assist::q_tab
};
if let Some(step) = complete(
&columns,
&line,
input.cursor_col(),
&value,
input.cursor(),
&mut cycle,
) {
input.replace_before_cursor(step.span, &step.insert);
sql_assist::landed(&mut cycle, input.value(), input.cursor());
}
self.prompt.sql_completion = cycle;
self.prompt.sql_columns = columns;
}
pub(crate) fn sql_column_matches(&self) -> Vec<&(String, DataType)> {
let input = self.query_input_shown();
let line = input.line_at(input.cursor_line()).unwrap_or_default();
let word = match self.prompt.query_mode {
QueryMode::Sql => sql_assist::word_before(line, input.cursor_col()),
QueryMode::Q => sql_assist::q_word_before(line, input.cursor_col()),
}
.map(|w| w.text)
.unwrap_or_default();
sql_assist::matching(&self.prompt.sql_columns, &word)
}
pub fn query_prompt_text(&self) -> Option<&str> {
self.query_prompt_mode()?;
Some(self.query_input_shown().value())
}
pub fn query_prompt_error(&self) -> Option<String> {
if let Some(error) = &self.prompt.query_run_error {
return Some(error.clone());
}
let state = self.data_table_state.as_ref()?;
let error = state.error()?;
Some(if self.prompt.query_mode == QueryMode::Sql {
crate::error_display::sql_error_message(error, state.sql_table_rows())
} else {
crate::error_display::user_message_from_polars(error)
})
}
pub fn inline_failures(&self) -> u64 {
self.prompt.inline_failures
}
pub(crate) fn run_query(&mut self, mode: QueryMode, text: &str, status: &str) {
self.prompt.query_run_error = None;
let Some(state) = self.data_table_state.as_mut() else {
return;
};
let rollback = state.rollback_point();
let rows = state.sql_table_rows();
state.deferred(|s| match mode {
QueryMode::Sql => s.sql_query(text.to_string()),
QueryMode::Q => s.query(text.to_string()),
});
if state.error().is_some() {
return;
}
self.prompt.query_running = Some(QueryRun {
origin: RunOrigin::Query(mode),
frame: state.len_generation(),
rollback,
counts: self.counting.markers(),
rows,
});
if !self.spawn_async_collect(status) {
self.prompt.query_running = None;
if self.query_prompt_mode() == Some(mode) {
self.leave_query_prompt_after_run();
}
}
}
pub(crate) fn query_run(&self) -> Option<&QueryRun> {
let run = self.prompt.query_running.as_ref()?;
let frame = self.data_table_state.as_ref()?.len_generation();
(run.frame == frame).then_some(run)
}
pub(crate) fn take_query_run(&mut self) -> Option<QueryRun> {
let run = self.prompt.query_running.take()?;
let frame = self.data_table_state.as_ref()?.len_generation();
(run.frame == frame).then_some(run)
}
pub(crate) fn leave_query_prompt_after_run(&mut self) {
self.prompt.sql_completion = None;
self.show_table();
self.prompt.sql_input.set_focused(false);
self.prompt.query_input.set_focused(false);
if let Some(state) = &mut self.data_table_state {
state.suppress_error_display = false;
}
}
pub(crate) fn sync_query_focus(&mut self) {
let mode = self.prompt.query_mode;
self.prompt.sql_input.set_focused(mode == QueryMode::Sql);
self.prompt.query_input.set_focused(mode == QueryMode::Q);
}
pub(crate) fn close_query_prompt(&mut self) {
self.prompt.query_run_error = None;
self.prompt.sql_completion = None;
self.prompt.query_input.clear();
self.prompt.sql_input.clear();
self.prompt.query_input.set_focused(false);
self.prompt.sql_input.set_focused(false);
self.show_table();
if let Some(state) = &mut self.data_table_state {
state.dismiss_error();
state.suppress_error_display = false;
}
}
}