use gpui_kit::component::kbd::Kbd;
use gpui_kit::component::{WindowExt, h_flex, v_flex};
use gpui_kit::prelude::*;
use gpui_kit::{App, Keystroke, Window, actions, div, px};
actions!(zippa_db, [ShowShortcuts]);
const WIDTH: f32 = 620.;
const HEIGHT: f32 = 520.;
struct Group {
title: &'static str,
rows: &'static [(&'static str, &'static [&'static str])],
}
const GROUPS: &[Group] = &[
Group {
title: "General",
rows: &[
("Show this list", &["ctrl-/"]),
("Quick switcher", &["secondary-k"]),
("Search the schema", &["secondary-shift-o"]),
("Settings", &["secondary-,"]),
(
"Close the settings window",
&["secondary-w", "alt-f4", "escape"],
),
],
},
Group {
title: "Connections",
rows: &[
("New connection (or another tab)", &["secondary-n"]),
("Close the active connection", &["secondary-shift-w"]),
("Disconnect the active connection", &["secondary-d"]),
("Next connection", &["secondary-shift-]"]),
("Previous connection", &["secondary-shift-["]),
("Connect from the connection editor", &["secondary-enter"]),
],
},
Group {
title: "Tabs",
rows: &[
("New query tab", &["secondary-t"]),
("Close the active tab", &["secondary-w"]),
("Next tab", &["ctrl-tab", "ctrl-pagedown"]),
("Previous tab", &["ctrl-shift-tab", "ctrl-pageup"]),
("Refresh the schema and the open table", &["secondary-r"]),
("Open the console of every statement sent", &["secondary-`"]),
("Open the process list", &["secondary-shift-p"]),
("Open the server variables", &["secondary-shift-v"]),
("Open the query digest", &["secondary-shift-d"]),
("Open SQLite maintenance", &["secondary-shift-m"]),
],
},
Group {
title: "Files",
rows: &[
("Open a SQL file", &["secondary-o"]),
("Import a SQL dump", &["secondary-shift-i"]),
("Save the active tab", &["secondary-s"]),
(
"Save the active tab under a new name",
&["secondary-shift-s"],
),
],
},
Group {
title: "Running SQL",
rows: &[
(
"Run the selection, or the statement at the caret",
&["secondary-enter"],
),
(
"Run every statement in the buffer, in one transaction",
&["secondary-shift-enter"],
),
(
"Run every statement without a transaction, skipping errors",
&["secondary-alt-shift-enter"],
),
(
"Show the statement's plan without running it",
&["secondary-e"],
),
(
"Show the plan and run the statement for actual times",
&["secondary-shift-e"],
),
("Stop a running query", &["secondary-."]),
],
},
Group {
title: "Editing SQL",
rows: &[
(
"Comment out the selected lines, or the caret's line",
&["secondary-/"],
),
("Copy the selection, or the caret's line", &["secondary-c"]),
("Cut the selection, or the caret's line", &["secondary-x"]),
(
"Delete the selected lines, or the caret's line",
&["secondary-shift-k"],
),
(
"Move the selected lines up or down",
&["alt-up", "alt-down"],
),
(
"Duplicate the selected lines above or below",
&["alt-shift-up", "alt-shift-down"],
),
(
"Select the next occurrence of the selection",
&["secondary-d"],
),
(
"Select the previous occurrence of the selection",
&["secondary-shift-d"],
),
("Select the caret's line", &["secondary-l"]),
(
"Go to the first non-blank character, then the line's start",
&["home"],
),
],
},
Group {
title: "Result grid",
rows: &[
("Copy the selected cell or rows", &["secondary-c"]),
("Copy the rows with a header line", &["secondary-shift-c"]),
("Open the cell's value in a dialog", &["secondary-shift-v"]),
("Pick the focused row", &["space"]),
("Extend the picked rows", &["shift-up", "shift-down"]),
("Pick every row", &["secondary-a"]),
("Unpick every row", &["secondary-shift-a", "escape"]),
],
},
Group {
title: "Editing a table",
rows: &[
("Edit the selected cell", &["enter"]),
("Set the selected cell to NULL", &["secondary-shift-n"]),
("Add a row to fill in", &["secondary-shift-i"]),
("Mark rows for deletion", &["secondary-backspace"]),
("Take the deletion mark off", &["secondary-shift-backspace"]),
("Apply staged changes", &["secondary-s"]),
("Discard staged changes", &["secondary-z"]),
("Toggle the row panel", &["secondary-\\"]),
("Save the value dialog", &["secondary-enter"]),
("Cancel a cell edit or dismiss a dialog", &["escape"]),
],
},
];
pub fn open(window: &mut Window, cx: &mut App) {
if window.has_active_dialog(cx) {
return;
}
window.open_dialog(cx, |dialog, _window, _cx| {
dialog
.w(px(WIDTH))
.h(px(HEIGHT))
.title("Keyboard shortcuts")
.overlay_closable(true)
.keyboard(true)
.child(body())
});
}
fn body() -> impl IntoElement {
div()
.id("shortcuts")
.size_full()
.overflow_y_scroll()
.child(v_flex().gap_4().children(GROUPS.iter().map(group)))
}
fn group(group: &Group) -> impl IntoElement {
v_flex()
.gap_1()
.child(
div()
.text_sm()
.font_weight(gpui_kit::FontWeight::BOLD)
.child(group.title),
)
.children(group.rows.iter().map(|(what, keys)| row(what, keys)))
}
fn row(what: &'static str, keys: &'static [&'static str]) -> impl IntoElement {
h_flex()
.justify_between()
.gap_4()
.text_sm()
.child(div().child(what))
.child(
h_flex()
.flex_none()
.gap_1()
.children(keys.iter().enumerate().flat_map(|(ix, keys)| {
let separator =
(ix > 0).then(|| div().text_xs().child("or").into_any_element());
let stroke = Keystroke::parse(keys)
.unwrap_or_else(|_| panic!("bad keystroke {keys:?} in the shortcut list"));
separator
.into_iter()
.chain([Kbd::new(stroke).into_any_element()])
})),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_listed_keystroke_parses() {
for group in GROUPS {
for (what, keys) in group.rows {
for keys in *keys {
assert!(Keystroke::parse(keys).is_ok(), "{what}: {keys:?}");
}
}
}
}
}