mod connections;
mod editors;
mod normal;
mod overlays;
pub mod types;
use crate::app::update::action::Action;
pub use connections::*;
pub use editors::*;
pub use normal::*;
pub use overlays::*;
pub use types::{Key, KeyCombo, Modifiers};
#[derive(Clone)]
pub struct KeyBinding {
pub key_short: &'static str,
pub key: &'static str,
pub desc_short: &'static str,
pub description: &'static str,
pub action: Action,
pub combos: &'static [KeyCombo],
}
impl KeyBinding {
pub const fn as_hint(&self) -> (&'static str, &'static str) {
(self.key_short, self.desc_short)
}
}
pub struct ExecBinding {
pub action: Action,
pub combos: &'static [KeyCombo],
}
pub struct ModeRow {
pub key_short: &'static str,
pub key: &'static str,
pub desc_short: &'static str,
pub description: &'static str,
pub bindings: &'static [ExecBinding],
}
impl ModeRow {
pub const fn as_hint(&self) -> (&'static str, &'static str) {
(self.key_short, self.desc_short)
}
}
pub struct ModeBindings {
pub rows: &'static [ModeRow],
}
impl ModeBindings {
pub fn resolve(&self, combo: &KeyCombo) -> Option<Action> {
crate::app::update::input::keymap::resolve_mode(combo, self.rows)
}
}
pub const HELP: ModeBindings = ModeBindings { rows: HELP_ROWS };
pub const CONNECTION_ERROR: ModeBindings = ModeBindings {
rows: CONNECTION_ERROR_ROWS,
};
pub const TABLE_PICKER: ModeBindings = ModeBindings {
rows: TABLE_PICKER_ROWS,
};
pub const ER_PICKER: ModeBindings = ModeBindings {
rows: ER_PICKER_ROWS,
};
pub const QUERY_HISTORY_PICKER: ModeBindings = ModeBindings {
rows: QUERY_HISTORY_PICKER_ROWS,
};
pub const COMMAND_PALETTE: ModeBindings = ModeBindings {
rows: COMMAND_PALETTE_ROWS,
};
pub const CONNECTION_SELECTOR: ModeBindings = ModeBindings {
rows: CONNECTION_SELECTOR_ROWS,
};
pub const ALL_MODE_BINDINGS: &[(&str, &ModeBindings)] = &[
("HELP", &HELP),
("CONNECTION_ERROR", &CONNECTION_ERROR),
("TABLE_PICKER", &TABLE_PICKER),
("ER_PICKER", &ER_PICKER),
("QUERY_HISTORY_PICKER", &QUERY_HISTORY_PICKER),
("COMMAND_PALETTE", &COMMAND_PALETTE),
("CONNECTION_SELECTOR", &CONNECTION_SELECTOR),
];
pub mod idx {
pub mod global {
pub const QUIT: usize = 0;
pub const HELP: usize = 1;
pub const TABLE_PICKER: usize = 2;
pub const PALETTE: usize = 3;
pub const COMMAND_LINE: usize = 4;
pub const FOCUS: usize = 5;
pub const EXIT_FOCUS: usize = 6;
pub const PANE_SWITCH: usize = 7;
pub const INSPECTOR_TABS: usize = 8;
pub const RELOAD: usize = 9;
pub const SQL: usize = 10;
pub const ER_DIAGRAM: usize = 11;
pub const CONNECTIONS: usize = 12;
pub const CSV_EXPORT: usize = 13;
pub const READ_ONLY: usize = 14;
pub const EXIT_READ_ONLY: usize = 15;
pub const QUERY_HISTORY: usize = 16;
}
pub mod footer_nav {
pub const SCROLL: usize = 0;
pub const SCROLL_SHORT: usize = 1;
pub const TOP_BOTTOM: usize = 2;
pub const H_SCROLL: usize = 3;
pub const PAGE_NAV: usize = 4;
}
pub mod sql_modal_normal {
pub const RUN: usize = 0;
pub const YANK: usize = 1;
pub const ENTER_INSERT: usize = 2;
pub const MOVE: usize = 3;
pub const HOME_END: usize = 4;
pub const CLOSE: usize = 5;
pub const CLEAR: usize = 6;
pub const QUERY_HISTORY: usize = 7;
}
pub mod sql_modal {
pub const RUN: usize = 0;
pub const ESC_NORMAL: usize = 1;
pub const MOVE: usize = 2;
pub const HOME_END: usize = 3;
pub const TAB: usize = 4;
pub const COMPLETION_TRIGGER: usize = 5;
pub const CLEAR: usize = 6;
pub const QUERY_HISTORY: usize = 7;
}
pub mod sql_modal_plan {
pub const EXPLAIN: usize = 0;
pub const ANALYZE: usize = 1;
pub const YANK: usize = 2;
pub const SCROLL: usize = 3;
pub const TAB: usize = 4;
pub const BACKTAB: usize = 5;
pub const CLOSE: usize = 6;
}
pub mod sql_modal_compare {
pub const EXPLAIN: usize = 0;
pub const ANALYZE: usize = 1;
pub const EDIT_QUERY: usize = 2;
pub const YANK: usize = 3;
pub const SCROLL: usize = 4;
pub const TAB: usize = 5;
pub const BACKTAB: usize = 6;
pub const CLOSE: usize = 7;
}
pub mod sql_modal_confirming {
pub const CANCEL_CONFIRM: usize = 0;
}
pub mod overlay {
pub const ESC_CANCEL: usize = 0;
pub const ESC_CLOSE: usize = 1;
pub const ENTER_EXECUTE: usize = 2;
pub const ENTER_SELECT: usize = 3;
pub const NAVIGATE_JK: usize = 4;
pub const TYPE_FILTER: usize = 5;
pub const ERROR_OPEN: usize = 6;
}
pub mod conn_setup {
pub const TAB_NAV: usize = 0;
pub const TAB_NEXT: usize = 1;
pub const TAB_PREV: usize = 2;
pub const SAVE: usize = 3;
pub const ESC_CANCEL: usize = 4;
pub const ENTER_DROPDOWN: usize = 5;
pub const DROPDOWN_NAV: usize = 6;
}
pub mod conn_error {
pub const EDIT: usize = 0;
pub const SWITCH: usize = 1;
pub const DETAILS: usize = 2;
pub const COPY: usize = 3;
pub const SCROLL: usize = 4;
pub const ESC_CLOSE: usize = 5;
pub const RETRY: usize = 6;
}
pub mod confirm {
pub const YES: usize = 0;
pub const NO: usize = 1;
}
pub mod table_picker {
pub const ENTER_SELECT: usize = 0;
pub const NAVIGATE: usize = 1;
pub const TYPE_FILTER: usize = 2;
pub const ESC_CLOSE: usize = 3;
}
pub mod er_picker {
pub const ENTER_GENERATE: usize = 0;
pub const SELECT: usize = 1;
pub const SELECT_ALL: usize = 2;
pub const NAVIGATE: usize = 3;
pub const TYPE_FILTER: usize = 4;
pub const ESC_CLOSE: usize = 5;
}
pub mod qh_picker {
pub const ENTER_SELECT: usize = 0;
pub const NAVIGATE: usize = 1;
pub const TYPE_FILTER: usize = 2;
pub const ESC_CLOSE: usize = 3;
}
pub mod cmd_palette {
pub const ENTER_EXECUTE: usize = 0;
pub const NAVIGATE_JK: usize = 1;
pub const ESC_CLOSE: usize = 2;
}
pub mod help {
pub const SCROLL: usize = 0;
pub const CLOSE: usize = 1;
}
pub mod result_active {
pub const ENTER_DEEPEN: usize = 0;
pub const YANK: usize = 1;
pub const STAGE_DELETE: usize = 2;
pub const UNSTAGE_DELETE: usize = 3;
pub const CELL_NAV: usize = 4;
pub const ROW_NAV: usize = 5;
pub const TOP_BOTTOM: usize = 6;
pub const ESC_BACK: usize = 7;
pub const EDIT: usize = 8;
pub const DRAFT_DISCARD: usize = 9;
pub const ROW_YANK: usize = 10;
}
pub mod cell_edit {
pub const WRITE: usize = 0;
pub const TYPE: usize = 1;
pub const MOVE: usize = 2;
pub const HOME_END: usize = 3;
pub const COMMAND: usize = 4;
pub const ESC_CANCEL: usize = 5;
}
pub mod connection_selector {
pub const CONFIRM: usize = 0;
pub const SELECT: usize = 1;
pub const NEW: usize = 2;
pub const EDIT: usize = 3;
pub const DELETE: usize = 4;
pub const CLOSE: usize = 5;
}
pub mod inspector_ddl {
pub const YANK: usize = 0;
}
pub mod history {
pub const OPEN: usize = 0;
pub const NAV: usize = 1;
pub const EXIT: usize = 2;
}
}
pub const fn help_content_line_count() -> usize {
const DEDUP_PAIRS: usize = 3;
const SECTION_COUNT: usize = 22;
const SECTION_HEADERS: usize = SECTION_COUNT;
const BLANK_SEPARATORS: usize = SECTION_COUNT - 1;
SECTION_HEADERS
+ BLANK_SEPARATORS
+ GLOBAL_KEYS.len()
+ NAVIGATION_KEYS.len()
+ HISTORY_KEYS.len()
- DEDUP_PAIRS
+ RESULT_ACTIVE_KEYS.len()
+ INSPECTOR_DDL_KEYS.len()
+ CELL_EDIT_KEYS.len()
+ SQL_MODAL_NORMAL_KEYS.len()
+ SQL_MODAL_KEYS.len()
+ SQL_MODAL_PLAN_KEYS.len()
+ SQL_MODAL_COMPARE_KEYS.len()
+ SQL_MODAL_CONFIRMING_KEYS.len()
+ OVERLAY_KEYS.len()
+ COMMAND_LINE_KEYS.len()
+ CONNECTION_SETUP_KEYS.len()
+ CONNECTION_ERROR_ROWS.len()
+ CONNECTION_SELECTOR_ROWS.len()
+ ER_PICKER_ROWS.len()
+ QUERY_HISTORY_PICKER_ROWS.len()
+ TABLE_PICKER_ROWS.len()
+ COMMAND_PALETTE_ROWS.len()
+ HELP_ROWS.len()
+ CONFIRM_DIALOG_KEYS.len()
}
pub fn is_quit(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::QUIT].combos.contains(combo)
}
pub fn is_help(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::HELP].combos.contains(combo)
}
pub fn is_table_picker(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::TABLE_PICKER]
.combos
.contains(combo)
}
pub fn is_command_palette(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::PALETTE].combos.contains(combo)
}
pub fn is_command_line(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::COMMAND_LINE]
.combos
.contains(combo)
}
pub fn is_focus_toggle(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::FOCUS].combos.contains(combo)
}
pub fn is_reload(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::RELOAD].combos.contains(combo)
}
pub fn is_open_sql(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::SQL].combos.contains(combo)
}
pub fn is_open_er(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::ER_DIAGRAM].combos.contains(combo)
}
pub fn is_csv_export(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::CSV_EXPORT].combos.contains(combo)
}
pub fn is_read_only_toggle(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::READ_ONLY].combos.contains(combo)
}
pub fn is_query_history(combo: &KeyCombo) -> bool {
GLOBAL_KEYS[idx::global::QUERY_HISTORY]
.combos
.contains(combo)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn idx_constants_are_within_bounds() {
assert!(idx::global::QUIT < GLOBAL_KEYS.len());
assert!(idx::global::HELP < GLOBAL_KEYS.len());
assert!(idx::global::TABLE_PICKER < GLOBAL_KEYS.len());
assert!(idx::global::PALETTE < GLOBAL_KEYS.len());
assert!(idx::global::COMMAND_LINE < GLOBAL_KEYS.len());
assert!(idx::global::FOCUS < GLOBAL_KEYS.len());
assert!(idx::global::EXIT_FOCUS < GLOBAL_KEYS.len());
assert!(idx::global::PANE_SWITCH < GLOBAL_KEYS.len());
assert!(idx::global::INSPECTOR_TABS < GLOBAL_KEYS.len());
assert!(idx::global::RELOAD < GLOBAL_KEYS.len());
assert!(idx::global::SQL < GLOBAL_KEYS.len());
assert!(idx::global::ER_DIAGRAM < GLOBAL_KEYS.len());
assert!(idx::global::CONNECTIONS < GLOBAL_KEYS.len());
assert!(idx::global::CSV_EXPORT < GLOBAL_KEYS.len());
assert!(idx::global::READ_ONLY < GLOBAL_KEYS.len());
assert!(idx::global::EXIT_READ_ONLY < GLOBAL_KEYS.len());
assert!(idx::global::QUERY_HISTORY < GLOBAL_KEYS.len());
assert!(idx::footer_nav::SCROLL < FOOTER_NAV_KEYS.len());
assert!(idx::footer_nav::SCROLL_SHORT < FOOTER_NAV_KEYS.len());
assert!(idx::footer_nav::TOP_BOTTOM < FOOTER_NAV_KEYS.len());
assert!(idx::footer_nav::H_SCROLL < FOOTER_NAV_KEYS.len());
assert!(idx::footer_nav::PAGE_NAV < FOOTER_NAV_KEYS.len());
assert!(idx::sql_modal_normal::RUN < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::YANK < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::ENTER_INSERT < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::MOVE < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::HOME_END < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::CLOSE < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::CLEAR < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal_normal::QUERY_HISTORY < SQL_MODAL_NORMAL_KEYS.len());
assert!(idx::sql_modal::RUN < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::ESC_NORMAL < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::MOVE < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::HOME_END < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::TAB < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::COMPLETION_TRIGGER < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::CLEAR < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal::QUERY_HISTORY < SQL_MODAL_KEYS.len());
assert!(idx::sql_modal_plan::EXPLAIN < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::ANALYZE < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::YANK < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::SCROLL < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::TAB < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::BACKTAB < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_plan::CLOSE < SQL_MODAL_PLAN_KEYS.len());
assert!(idx::sql_modal_compare::EXPLAIN < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::ANALYZE < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::EDIT_QUERY < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::YANK < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::SCROLL < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::TAB < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::BACKTAB < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_compare::CLOSE < SQL_MODAL_COMPARE_KEYS.len());
assert!(idx::sql_modal_confirming::CANCEL_CONFIRM < SQL_MODAL_CONFIRMING_KEYS.len());
assert!(idx::overlay::ESC_CANCEL < OVERLAY_KEYS.len());
assert!(idx::overlay::ESC_CLOSE < OVERLAY_KEYS.len());
assert!(idx::overlay::ENTER_EXECUTE < OVERLAY_KEYS.len());
assert!(idx::overlay::ENTER_SELECT < OVERLAY_KEYS.len());
assert!(idx::overlay::NAVIGATE_JK < OVERLAY_KEYS.len());
assert!(idx::overlay::TYPE_FILTER < OVERLAY_KEYS.len());
assert!(idx::overlay::ERROR_OPEN < OVERLAY_KEYS.len());
assert!(idx::conn_setup::TAB_NAV < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::TAB_NEXT < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::TAB_PREV < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::SAVE < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::ESC_CANCEL < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::ENTER_DROPDOWN < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_setup::DROPDOWN_NAV < CONNECTION_SETUP_KEYS.len());
assert!(idx::conn_error::EDIT < CONNECTION_ERROR_ROWS.len());
assert!(idx::conn_error::SWITCH < CONNECTION_ERROR_ROWS.len());
assert!(idx::conn_error::DETAILS < CONNECTION_ERROR_ROWS.len());
assert!(idx::conn_error::COPY < CONNECTION_ERROR_ROWS.len());
assert!(idx::conn_error::SCROLL < CONNECTION_ERROR_ROWS.len());
assert!(idx::conn_error::ESC_CLOSE < CONNECTION_ERROR_ROWS.len());
assert!(idx::confirm::YES < CONFIRM_DIALOG_KEYS.len());
assert!(idx::confirm::NO < CONFIRM_DIALOG_KEYS.len());
assert!(idx::table_picker::ENTER_SELECT < TABLE_PICKER_ROWS.len());
assert!(idx::table_picker::NAVIGATE < TABLE_PICKER_ROWS.len());
assert!(idx::table_picker::TYPE_FILTER < TABLE_PICKER_ROWS.len());
assert!(idx::table_picker::ESC_CLOSE < TABLE_PICKER_ROWS.len());
assert!(idx::er_picker::ENTER_GENERATE < ER_PICKER_ROWS.len());
assert!(idx::er_picker::SELECT < ER_PICKER_ROWS.len());
assert!(idx::er_picker::SELECT_ALL < ER_PICKER_ROWS.len());
assert!(idx::er_picker::NAVIGATE < ER_PICKER_ROWS.len());
assert!(idx::er_picker::TYPE_FILTER < ER_PICKER_ROWS.len());
assert!(idx::er_picker::ESC_CLOSE < ER_PICKER_ROWS.len());
assert!(idx::qh_picker::ENTER_SELECT < QUERY_HISTORY_PICKER_ROWS.len());
assert!(idx::qh_picker::NAVIGATE < QUERY_HISTORY_PICKER_ROWS.len());
assert!(idx::qh_picker::TYPE_FILTER < QUERY_HISTORY_PICKER_ROWS.len());
assert!(idx::qh_picker::ESC_CLOSE < QUERY_HISTORY_PICKER_ROWS.len());
assert!(idx::cmd_palette::ENTER_EXECUTE < COMMAND_PALETTE_ROWS.len());
assert!(idx::cmd_palette::NAVIGATE_JK < COMMAND_PALETTE_ROWS.len());
assert!(idx::cmd_palette::ESC_CLOSE < COMMAND_PALETTE_ROWS.len());
assert!(idx::help::SCROLL < HELP_ROWS.len());
assert!(idx::help::CLOSE < HELP_ROWS.len());
assert!(idx::result_active::ENTER_DEEPEN < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::YANK < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::STAGE_DELETE < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::UNSTAGE_DELETE < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::CELL_NAV < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::ROW_NAV < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::TOP_BOTTOM < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::ESC_BACK < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::EDIT < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::DRAFT_DISCARD < RESULT_ACTIVE_KEYS.len());
assert!(idx::result_active::ROW_YANK < RESULT_ACTIVE_KEYS.len());
assert!(idx::history::OPEN < HISTORY_KEYS.len());
assert!(idx::history::NAV < HISTORY_KEYS.len());
assert!(idx::history::EXIT < HISTORY_KEYS.len());
assert!(idx::inspector_ddl::YANK < INSPECTOR_DDL_KEYS.len());
assert!(idx::cell_edit::WRITE < CELL_EDIT_KEYS.len());
assert!(idx::cell_edit::TYPE < CELL_EDIT_KEYS.len());
assert!(idx::cell_edit::MOVE < CELL_EDIT_KEYS.len());
assert!(idx::cell_edit::HOME_END < CELL_EDIT_KEYS.len());
assert!(idx::cell_edit::COMMAND < CELL_EDIT_KEYS.len());
assert!(idx::cell_edit::ESC_CANCEL < CELL_EDIT_KEYS.len());
assert!(idx::connection_selector::CONFIRM < CONNECTION_SELECTOR_ROWS.len());
assert!(idx::connection_selector::SELECT < CONNECTION_SELECTOR_ROWS.len());
assert!(idx::connection_selector::NEW < CONNECTION_SELECTOR_ROWS.len());
assert!(idx::connection_selector::EDIT < CONNECTION_SELECTOR_ROWS.len());
assert!(idx::connection_selector::DELETE < CONNECTION_SELECTOR_ROWS.len());
assert!(idx::connection_selector::CLOSE < CONNECTION_SELECTOR_ROWS.len());
}
#[test]
fn help_content_line_count_matches_section_structure() {
let sections: &[usize] = &[
GLOBAL_KEYS.len(),
NAVIGATION_KEYS.len(),
HISTORY_KEYS.len(),
RESULT_ACTIVE_KEYS.len(),
INSPECTOR_DDL_KEYS.len(),
CELL_EDIT_KEYS.len(),
SQL_MODAL_NORMAL_KEYS.len(),
SQL_MODAL_KEYS.len(),
SQL_MODAL_PLAN_KEYS.len(),
SQL_MODAL_COMPARE_KEYS.len(),
SQL_MODAL_CONFIRMING_KEYS.len(),
OVERLAY_KEYS.len(),
COMMAND_LINE_KEYS.len(),
CONNECTION_SETUP_KEYS.len(),
CONNECTION_ERROR_ROWS.len(),
CONNECTION_SELECTOR_ROWS.len(),
ER_PICKER_ROWS.len(),
QUERY_HISTORY_PICKER_ROWS.len(),
TABLE_PICKER_ROWS.len(),
COMMAND_PALETTE_ROWS.len(),
HELP_ROWS.len(),
CONFIRM_DIALOG_KEYS.len(),
];
let section_count = sections.len();
let dedup_pairs = 3; let expected: usize =
section_count + sections.iter().sum::<usize>() + (section_count - 1) - dedup_pairs;
assert_eq!(help_content_line_count(), expected);
}
mod semantic {
use super::*;
use crate::app::update::input::keymap;
use rstest::rstest;
#[rstest]
#[case(idx::global::QUIT, Action::Quit)]
#[case(idx::global::HELP, Action::OpenHelp)]
#[case(idx::global::TABLE_PICKER, Action::OpenTablePicker)]
#[case(idx::global::PALETTE, Action::OpenCommandPalette)]
#[case(idx::global::COMMAND_LINE, Action::EnterCommandLine)]
#[case(idx::global::RELOAD, Action::ReloadMetadata)]
#[case(idx::global::SQL, Action::OpenSqlModal)]
#[case(idx::global::ER_DIAGRAM, Action::OpenErTablePicker)]
#[case(idx::global::CONNECTIONS, Action::OpenConnectionSelector)]
#[case(idx::global::CSV_EXPORT, Action::RequestCsvExport)]
#[case(idx::global::READ_ONLY, Action::ToggleReadOnly)]
#[case(idx::global::EXIT_READ_ONLY, Action::ToggleReadOnly)]
#[case(idx::global::QUERY_HISTORY, Action::OpenQueryHistoryPicker)]
fn global_key_action_matches(#[case] i: usize, #[case] expected: Action) {
assert!(
std::mem::discriminant(&GLOBAL_KEYS[i].action) == std::mem::discriminant(&expected),
"GLOBAL_KEYS[{i}] has action {:?}, expected {expected:?}",
GLOBAL_KEYS[i].action
);
}
#[rstest]
#[case(idx::confirm::YES, Action::ConfirmDialogConfirm)]
#[case(idx::confirm::NO, Action::ConfirmDialogCancel)]
fn confirm_key_action_matches(#[case] i: usize, #[case] expected: Action) {
assert!(
std::mem::discriminant(&CONFIRM_DIALOG_KEYS[i].action)
== std::mem::discriminant(&expected),
"CONFIRM_DIALOG_KEYS[{i}] has action {:?}, expected {expected:?}",
CONFIRM_DIALOG_KEYS[i].action
);
}
#[rstest]
#[case(idx::sql_modal_plan::EXPLAIN, Action::ExplainRequest)]
#[case(idx::sql_modal_plan::ANALYZE, Action::ExplainAnalyzeRequest)]
#[case(idx::sql_modal_plan::YANK, Action::SqlModalYank)]
#[case(idx::sql_modal_plan::TAB, Action::SqlModalNextTab)]
#[case(idx::sql_modal_plan::BACKTAB, Action::SqlModalPrevTab)]
#[case(idx::sql_modal_plan::CLOSE, Action::CloseSqlModal)]
fn plan_key_action_matches(#[case] i: usize, #[case] expected: Action) {
assert!(
std::mem::discriminant(&SQL_MODAL_PLAN_KEYS[i].action)
== std::mem::discriminant(&expected),
"SQL_MODAL_PLAN_KEYS[{i}] has action {:?}, expected {expected:?}",
SQL_MODAL_PLAN_KEYS[i].action
);
}
#[rstest]
#[case(idx::sql_modal_compare::EXPLAIN, Action::ExplainRequest)]
#[case(idx::sql_modal_compare::ANALYZE, Action::ExplainAnalyzeRequest)]
#[case(idx::sql_modal_compare::EDIT_QUERY, Action::CompareEditQuery)]
#[case(idx::sql_modal_compare::YANK, Action::SqlModalYank)]
#[case(idx::sql_modal_compare::TAB, Action::SqlModalNextTab)]
#[case(idx::sql_modal_compare::BACKTAB, Action::SqlModalPrevTab)]
#[case(idx::sql_modal_compare::CLOSE, Action::CloseSqlModal)]
fn compare_key_action_matches(#[case] i: usize, #[case] expected: Action) {
assert!(
std::mem::discriminant(&SQL_MODAL_COMPARE_KEYS[i].action)
== std::mem::discriminant(&expected),
"SQL_MODAL_COMPARE_KEYS[{i}] has action {:?}, expected {expected:?}",
SQL_MODAL_COMPARE_KEYS[i].action
);
}
fn check_non_none_have_combos(bindings: &[KeyBinding], name: &str) {
for (i, kb) in bindings.iter().enumerate() {
if !matches!(kb.action, Action::None) && kb.combos.is_empty() {
if kb.key.starts_with(':') {
continue;
}
if kb.key_short == ":w" || kb.desc_short == "Write" {
continue;
}
panic!(
"{name}[{i}] has action {:?} but no combos (key={:?})",
kb.action, kb.key
);
}
}
}
#[test]
fn all_non_none_bindings_have_combos() {
check_non_none_have_combos(GLOBAL_KEYS, "GLOBAL_KEYS");
check_non_none_have_combos(CONFIRM_DIALOG_KEYS, "CONFIRM_DIALOG_KEYS");
check_non_none_have_combos(COMMAND_LINE_KEYS, "COMMAND_LINE_KEYS");
check_non_none_have_combos(CELL_EDIT_KEYS, "CELL_EDIT_KEYS");
check_non_none_have_combos(HISTORY_KEYS, "HISTORY_KEYS");
}
fn check_mode_rows_exec_valid(rows: &[ModeRow], name: &str) {
for (i, row) in rows.iter().enumerate() {
for (j, eb) in row.bindings.iter().enumerate() {
assert!(
!eb.combos.is_empty(),
"{name}[{i}].bindings[{j}] has action {:?} but no combos",
eb.action
);
assert!(
!matches!(eb.action, Action::None),
"{name}[{i}].bindings[{j}] has Action::None in exec binding",
);
}
}
}
#[test]
fn all_mode_row_exec_entries_are_valid() {
for (name, mb) in ALL_MODE_BINDINGS {
check_mode_rows_exec_valid(mb.rows, name);
}
}
fn check_no_duplicate_combos(bindings: &[KeyBinding], name: &str) {
let mut seen: Vec<KeyCombo> = Vec::new();
for kb in bindings
.iter()
.filter(|kb| !matches!(kb.action, Action::None))
{
for combo in kb.combos {
assert!(
!seen.contains(combo),
"{name}: duplicate combo {combo:?} in binding {:?}",
kb.action
);
seen.push(*combo);
}
}
}
fn check_no_duplicate_combos_rows(rows: &[ModeRow], name: &str) {
let mut seen: Vec<KeyCombo> = Vec::new();
for row in rows {
for eb in row.bindings {
for combo in eb.combos {
assert!(
!seen.contains(combo),
"{name}: duplicate combo {combo:?} in binding {:?}",
eb.action
);
seen.push(*combo);
}
}
}
}
#[test]
fn no_duplicate_combos_in_simple_modes() {
check_no_duplicate_combos(CONFIRM_DIALOG_KEYS, "CONFIRM_DIALOG_KEYS");
check_no_duplicate_combos(COMMAND_LINE_KEYS, "COMMAND_LINE_KEYS");
for (name, mb) in ALL_MODE_BINDINGS {
check_no_duplicate_combos_rows(mb.rows, name);
}
}
fn check_keymap_roundtrip(bindings: &[KeyBinding], name: &str) {
for kb in bindings
.iter()
.filter(|kb| !matches!(kb.action, Action::None))
{
for combo in kb.combos {
let resolved = keymap::resolve(combo, bindings);
match resolved {
Some(ref action)
if std::mem::discriminant(action)
== std::mem::discriminant(&kb.action) => {}
other => panic!(
"{name}: combo {combo:?} resolved to {other:?}, expected {:?}",
kb.action
),
}
}
}
}
fn check_resolve_mode_roundtrip(rows: &[ModeRow], name: &str) {
for row in rows {
for eb in row.bindings {
for combo in eb.combos {
let resolved = keymap::resolve_mode(combo, rows);
match resolved {
Some(ref action)
if std::mem::discriminant(action)
== std::mem::discriminant(&eb.action) => {}
other => panic!(
"{name}: combo {combo:?} resolved to {other:?}, expected {:?}",
eb.action
),
}
}
}
}
}
#[test]
fn keymap_resolve_roundtrip_for_simple_modes() {
check_keymap_roundtrip(CONFIRM_DIALOG_KEYS, "CONFIRM_DIALOG_KEYS");
check_keymap_roundtrip(COMMAND_LINE_KEYS, "COMMAND_LINE_KEYS");
for (name, mb) in ALL_MODE_BINDINGS {
check_resolve_mode_roundtrip(mb.rows, name);
}
}
fn check_no_plain_char_in_filter_mode(
bindings: &[KeyBinding],
name: &str,
allowed_chars: &[char],
) {
let no_mods = Modifiers {
ctrl: false,
alt: false,
shift: false,
};
for kb in bindings
.iter()
.filter(|kb| !matches!(kb.action, Action::None))
{
for combo in kb.combos {
if combo.modifiers == no_mods
&& let Key::Char(c) = combo.key
{
assert!(
allowed_chars.contains(&c),
"{name}: executable entry {:?} has plain Char({c:?}) combo \
which would shadow filter input",
kb.action
);
}
}
}
}
fn check_no_plain_char_in_filter_mode_rows(
rows: &[ModeRow],
name: &str,
allowed_chars: &[char],
) {
let no_mods = Modifiers {
ctrl: false,
alt: false,
shift: false,
};
for row in rows {
for eb in row.bindings {
for combo in eb.combos {
if combo.modifiers == no_mods
&& let Key::Char(c) = combo.key
{
assert!(
allowed_chars.contains(&c),
"{name}: executable entry {:?} has plain Char({c:?}) combo \
which would shadow filter input",
eb.action
);
}
}
}
}
}
#[test]
fn table_picker_has_no_plain_char_combos() {
check_no_plain_char_in_filter_mode_rows(TABLE_PICKER_ROWS, "TABLE_PICKER_ROWS", &[]);
}
#[test]
fn er_picker_has_no_plain_char_combos() {
check_no_plain_char_in_filter_mode_rows(ER_PICKER_ROWS, "ER_PICKER_ROWS", &[' ']);
}
#[test]
fn query_history_picker_has_no_plain_char_combos() {
check_no_plain_char_in_filter_mode_rows(
QUERY_HISTORY_PICKER_ROWS,
"QUERY_HISTORY_PICKER_ROWS",
&[],
);
}
#[test]
fn command_line_has_no_problematic_plain_char_combos() {
check_no_plain_char_in_filter_mode(COMMAND_LINE_KEYS, "COMMAND_LINE_KEYS", &[]);
}
#[test]
fn cell_edit_plain_char_combos_are_intentional() {
check_no_plain_char_in_filter_mode(CELL_EDIT_KEYS, "CELL_EDIT_KEYS", &[':']);
}
fn check_none_action_entries_have_no_combos(bindings: &[KeyBinding], name: &str) {
for (i, kb) in bindings.iter().enumerate() {
assert!(
!matches!(kb.action, Action::None) || kb.combos.is_empty(),
"{name}[{i}] has action Action::None but non-empty combos: {:?}",
kb.combos
);
}
}
#[test]
fn none_action_entries_have_no_combos() {
check_none_action_entries_have_no_combos(GLOBAL_KEYS, "GLOBAL_KEYS");
check_none_action_entries_have_no_combos(NAVIGATION_KEYS, "NAVIGATION_KEYS");
check_none_action_entries_have_no_combos(FOOTER_NAV_KEYS, "FOOTER_NAV_KEYS");
check_none_action_entries_have_no_combos(
SQL_MODAL_NORMAL_KEYS,
"SQL_MODAL_NORMAL_KEYS",
);
check_none_action_entries_have_no_combos(SQL_MODAL_KEYS, "SQL_MODAL_KEYS");
check_none_action_entries_have_no_combos(SQL_MODAL_PLAN_KEYS, "SQL_MODAL_PLAN_KEYS");
check_none_action_entries_have_no_combos(
SQL_MODAL_COMPARE_KEYS,
"SQL_MODAL_COMPARE_KEYS",
);
check_none_action_entries_have_no_combos(
SQL_MODAL_CONFIRMING_KEYS,
"SQL_MODAL_CONFIRMING_KEYS",
);
check_none_action_entries_have_no_combos(OVERLAY_KEYS, "OVERLAY_KEYS");
check_none_action_entries_have_no_combos(
CONNECTION_SETUP_KEYS,
"CONNECTION_SETUP_KEYS",
);
check_none_action_entries_have_no_combos(RESULT_ACTIVE_KEYS, "RESULT_ACTIVE_KEYS");
check_none_action_entries_have_no_combos(HISTORY_KEYS, "HISTORY_KEYS");
}
#[test]
fn all_mode_bindings_count() {
assert_eq!(ALL_MODE_BINDINGS.len(), 7);
}
}
}