diff --git a/README.md b/README.md index f457cb2..0e1d90d 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,8 @@ Editor for executing SQL with local DataFusion `SessionContext`. - Editable - Character keys to write queries - Backspace / tab / enter work same as normal + - `Shift` + Up/Down/Left/Right => Select text + - `Alt` + `Enter` => execute query - `esc` to exit Edit mode and go back to Normal mode - DDL mode - Not editable @@ -257,6 +259,8 @@ Editor for executing SQL with local DataFusion `SessionContext`. - Editable - Character keys to write queries - Backspace / tab / enter work same as normal + - `Shift` + Up/Down/Left/Right => Select text + - `Alt` + `Enter` => execute query - `esc` to exit Edit mode and go back to Normal mode #### FlightSQL Tab @@ -273,6 +277,8 @@ Same interface as SQL tab but sends SQL queries to FlightSQL server. - Edit mode - Character keys to write queries - Backspace / tab / enter work same as normal + - `Shift` + Up/Down/Left/Right => Select text + - `Alt` + `Enter` => execute query - `esc` to exit Edit mode and go back to Normal mode #### History Tab diff --git a/src/tui/handlers/flightsql.rs b/src/tui/handlers/flightsql.rs index b434b41..b4a03fe 100644 --- a/src/tui/handlers/flightsql.rs +++ b/src/tui/handlers/flightsql.rs @@ -93,6 +93,17 @@ pub fn editable_handler(app: &mut App, key: KeyEvent) { (KeyCode::Right, KeyModifiers::ALT) => app.state.flightsql_tab.next_word(), (KeyCode::Backspace, KeyModifiers::ALT) => app.state.flightsql_tab.delete_word(), (KeyCode::Esc, _) => app.state.flightsql_tab.exit_edit(), + (KeyCode::Enter, KeyModifiers::ALT) => { + // TODO: Encapsulate this logic + let sql = app.state.sql_tab.sql(); + info!("Running query: {}", sql); + let _event_tx = app.event_tx().clone(); + let execution = Arc::clone(&app.execution); + let sqls: Vec = sql.split(';').map(|s| s.to_string()).collect(); + let handle = tokio::spawn(execution.run_sqls(sqls, _event_tx)); + app.state.sql_tab.set_execution_task(handle); + } + _ => app.state.flightsql_tab.update_editor_content(key), } } diff --git a/src/tui/handlers/sql.rs b/src/tui/handlers/sql.rs index 158a153..8ba14e1 100644 --- a/src/tui/handlers/sql.rs +++ b/src/tui/handlers/sql.rs @@ -122,6 +122,29 @@ pub fn editable_handler(app: &mut App, key: KeyEvent) { (KeyCode::Right, KeyModifiers::ALT) => app.state.sql_tab.next_word(), (KeyCode::Backspace, KeyModifiers::ALT) => app.state.sql_tab.delete_word(), (KeyCode::Esc, _) => app.state.sql_tab.exit_edit(), + (KeyCode::Enter, KeyModifiers::ALT) => { + match app.state.sql_tab.mode() { + // TODO: Encapsulate this logic + SQLTabMode::Normal => { + let sql = app.state.sql_tab.sql(); + info!("Running query: {}", sql); + let _event_tx = app.event_tx().clone(); + let execution = Arc::clone(&app.execution); + let sqls: Vec = sql.split(';').map(|s| s.to_string()).collect(); + let handle = tokio::spawn(execution.run_sqls(sqls, _event_tx)); + app.state.sql_tab.set_execution_task(handle); + } + SQLTabMode::DDL => { + let _event_tx = app.event_tx().clone(); + // TODO: Probably want this to load from Editor instead of the file so that + // it is the latest content. + let ddl = app.execution.load_ddl().unwrap_or_default(); + if let Err(e) = _event_tx.send(AppEvent::ExecuteDDL(ddl)) { + error!("Error sending ExecuteDDL event: {:?}", e); + } + } + } + } _ => app.state.sql_tab.update_editor_content(key), } } diff --git a/src/tui/state/tabs/flightsql.rs b/src/tui/state/tabs/flightsql.rs index dd972c2..1b7fd85 100644 --- a/src/tui/state/tabs/flightsql.rs +++ b/src/tui/state/tabs/flightsql.rs @@ -274,8 +274,38 @@ impl<'app> FlightSQLTabState<'app> { } } + /// Returns the SQL to be executed. If no text is selected it returns the entire buffer else + /// it returns the current selection. pub fn sql(&self) -> String { - self.editor.lines().join("\n") + if let Some(((start_row, start_col), (end_row, end_col))) = self.editor.selection_range() { + if start_row == end_row { + let line = &self.editor.lines()[start_row]; + line.chars() + .skip(start_col) + .take(end_col - start_col) + .collect() + } else { + let lines: Vec = self + .editor + .lines() + .iter() + .enumerate() + .map(|(i, line)| { + let selected_chars: Vec = if i == start_row { + line.chars().skip(start_col).collect() + } else if i == end_row { + line.chars().take(end_col).collect() + } else { + line.chars().collect() + }; + selected_chars.into_iter().collect() + }) + .collect(); + lines.join("\n") + } + } else { + self.editor.lines().join("\n") + } } } diff --git a/src/tui/state/tabs/sql.rs b/src/tui/state/tabs/sql.rs index 67bc060..ed7dadb 100644 --- a/src/tui/state/tabs/sql.rs +++ b/src/tui/state/tabs/sql.rs @@ -301,9 +301,43 @@ impl<'app> SQLTabState<'app> { self.mode = mode } + /// Returns the SQL to be executed. If no text is selected it returns the entire buffer else + /// it returns the current selection. For DDL it returns the entire buffer. pub fn sql(&self) -> String { match self.mode { - SQLTabMode::Normal => self.editor.lines().join("\n"), + SQLTabMode::Normal => { + if let Some(((start_row, start_col), (end_row, end_col))) = + self.editor.selection_range() + { + if start_row == end_row { + let line = &self.editor.lines()[start_row]; + line.chars() + .skip(start_col) + .take(end_col - start_col) + .collect() + } else { + let lines: Vec = self + .editor + .lines() + .iter() + .enumerate() + .map(|(i, line)| { + let selected_chars: Vec = if i == start_row { + line.chars().skip(start_col).collect() + } else if i == end_row { + line.chars().take(end_col).collect() + } else { + line.chars().collect() + }; + selected_chars.into_iter().collect() + }) + .collect(); + lines.join("\n") + } + } else { + self.editor.lines().join("\n") + } + } SQLTabMode::DDL => self.ddl_editor.lines().join("\n"), } }