1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! Key bindings.
//!
//! GPUI resolves `secondary` per platform: Cmd on macOS, Ctrl on Linux and
//! Windows. Every shortcut here uses it so the app matches the OS it runs on.
use gpui_kit::{App, KeyBinding, NoAction};
use crate::app::{CloseConnection, NewConnection, NextConnection, PreviousConnection};
use crate::ui::data_grid::{
ClearRowSelection, CopyValue, CopyWithHeaders, ExtendSelectionDown, ExtendSelectionUp,
SelectAllRows, ToggleRow, ViewCell,
};
use crate::ui::import_dialog::CloseImport;
use crate::ui::query_editor::{
CopyLine, CutLine, DeleteLine, DuplicateLineDown, DuplicateLineUp, Explain, ExplainAnalyze,
MoveLineDown, MoveLineUp, RunQuery, RunScript, RunScriptIgnoringErrors, SelectLine,
SelectNextOccurrence, SelectPreviousOccurrence, SmartHome, ToggleComment,
};
use crate::ui::session::{
CancelQuery, CloseTab, Disconnect, ImportSqlDump, NewTab, NextTab, OpenConsole, OpenFile,
OpenMaintenance, OpenProcessList, OpenQueryDigest, OpenServerVariables, PreviousTab,
QuickSwitcher, Reconnect, Refresh, SaveFile, SaveFileAs, SearchSchema,
};
use crate::ui::settings_window::{CloseSettings, OpenSettings};
use crate::ui::shortcuts_dialog::ShowShortcuts;
use crate::ui::table_view::{
ApplyEdits, CancelEdit, DeleteRows, DiscardEdits, EditCell, InsertRow, RestoreRows, SetNull,
ToggleRowPanel,
};
use crate::ui::value_dialog::{CloseValue, SaveValue};
use crate::ui::welcome::{EditorClose, EditorConnect};
pub fn bind(cx: &mut App) {
cx.bind_keys([
// Quick switcher / fuzzy object palette across tabs, tables, views, databases, and actions.
KeyBinding::new("secondary-k", QuickSwitcher, Some("Session")),
KeyBinding::new("secondary-k", QuickSwitcher, Some("QueryEditor > Input")),
KeyBinding::new("secondary-k", QuickSwitcher, Some("QueryEditor")),
KeyBinding::new("secondary-k", QuickSwitcher, Some("TableView")),
KeyBinding::new("secondary-k", QuickSwitcher, Some("TableView > DataTable")),
KeyBinding::new(
"secondary-k",
QuickSwitcher,
Some("TableView > DataTable > Input"),
),
KeyBinding::new("secondary-k", QuickSwitcher, Some("DataGrid > DataTable")),
KeyBinding::new("secondary-k", QuickSwitcher, Some("Workspace")),
// Schema search: every column, index, routine and trigger, not just
// the names the quick switcher knows. The shifted key of the quick
// switcher, and bound everywhere it is.
KeyBinding::new("secondary-shift-o", SearchSchema, Some("Session")),
KeyBinding::new(
"secondary-shift-o",
SearchSchema,
Some("QueryEditor > Input"),
),
KeyBinding::new("secondary-shift-o", SearchSchema, Some("QueryEditor")),
KeyBinding::new("secondary-shift-o", SearchSchema, Some("TableView")),
KeyBinding::new(
"secondary-shift-o",
SearchSchema,
Some("TableView > DataTable"),
),
KeyBinding::new(
"secondary-shift-o",
SearchSchema,
Some("TableView > DataTable > Input"),
),
KeyBinding::new(
"secondary-shift-o",
SearchSchema,
Some("DataGrid > DataTable"),
),
KeyBinding::new("secondary-shift-o", SearchSchema, Some("Workspace")),
// The editor's own `Input` context already binds `secondary-enter`
// (it would insert a newline), so this binds the more specific
// "Input inside a QueryEditor" to win at that node.
KeyBinding::new("secondary-enter", RunQuery, Some("QueryEditor > Input")),
// Running the whole buffer is the rarer of the two, so it takes the
// extra modifier.
KeyBinding::new(
"secondary-shift-enter",
RunScript,
Some("QueryEditor > Input"),
),
KeyBinding::new("secondary-shift-enter", RunScript, Some("QueryEditor")),
// The whole buffer again, without a transaction and past every
// failure — re-running a migration that was partly applied before.
KeyBinding::new(
"secondary-alt-shift-enter",
RunScriptIgnoringErrors,
Some("QueryEditor > Input"),
),
KeyBinding::new(
"secondary-alt-shift-enter",
RunScriptIgnoringErrors,
Some("QueryEditor"),
),
// Reading the plan, with and without running the statement. The
// editor's own context leaves both free.
KeyBinding::new("secondary-e", Explain, Some("QueryEditor > Input")),
KeyBinding::new("secondary-e", Explain, Some("QueryEditor")),
KeyBinding::new(
"secondary-shift-e",
ExplainAnalyze,
Some("QueryEditor > Input"),
),
KeyBinding::new("secondary-shift-e", ExplainAnalyze, Some("QueryEditor")),
// Toggle commenting on the caret's line or the selected lines.
KeyBinding::new("secondary-/", ToggleComment, Some("QueryEditor > Input")),
KeyBinding::new("secondary-/", ToggleComment, Some("QueryEditor")),
// The line operations, with VS Code's shape: with a selection they act
// on it, otherwise on the caret's line. Copy and cut take the line
// plus its newline, so pasting yields a whole line.
KeyBinding::new("secondary-c", CopyLine, Some("QueryEditor > Input")),
KeyBinding::new("secondary-c", CopyLine, Some("QueryEditor")),
KeyBinding::new("secondary-x", CutLine, Some("QueryEditor > Input")),
KeyBinding::new("secondary-x", CutLine, Some("QueryEditor")),
KeyBinding::new("secondary-shift-k", DeleteLine, Some("QueryEditor > Input")),
KeyBinding::new("secondary-shift-k", DeleteLine, Some("QueryEditor")),
KeyBinding::new("alt-up", MoveLineUp, Some("QueryEditor > Input")),
KeyBinding::new("alt-up", MoveLineUp, Some("QueryEditor")),
KeyBinding::new("alt-down", MoveLineDown, Some("QueryEditor > Input")),
KeyBinding::new("alt-down", MoveLineDown, Some("QueryEditor")),
KeyBinding::new("alt-shift-up", DuplicateLineUp, Some("QueryEditor > Input")),
KeyBinding::new("alt-shift-up", DuplicateLineUp, Some("QueryEditor")),
KeyBinding::new(
"alt-shift-down",
DuplicateLineDown,
Some("QueryEditor > Input"),
),
KeyBinding::new("alt-shift-down", DuplicateLineDown, Some("QueryEditor")),
// Occurrence selection. The editor's narrower context wins over the
// session's disconnect and query digest on these keys, the way the
// quick switcher already does on its own.
KeyBinding::new(
"secondary-d",
SelectNextOccurrence,
Some("QueryEditor > Input"),
),
KeyBinding::new("secondary-d", SelectNextOccurrence, Some("QueryEditor")),
KeyBinding::new(
"secondary-shift-d",
SelectPreviousOccurrence,
Some("QueryEditor > Input"),
),
KeyBinding::new(
"secondary-shift-d",
SelectPreviousOccurrence,
Some("QueryEditor"),
),
// The caret's whole line; pressing again extends through the next one.
KeyBinding::new("secondary-l", SelectLine, Some("QueryEditor > Input")),
KeyBinding::new("secondary-l", SelectLine, Some("QueryEditor")),
// Home goes to the first non-blank character first, then the line's
// start. ⌘◀ does the same on macOS; elsewhere `cmd` is the Super key,
// where the binding is inert, leaving Ctrl+◀ to word movement.
KeyBinding::new("home", SmartHome, Some("QueryEditor > Input")),
KeyBinding::new("cmd-left", SmartHome, Some("QueryEditor > Input")),
// Stop a query that is taking too long.
KeyBinding::new("secondary-.", CancelQuery, Some("Session")),
// Import a SQL dump into the connection. The table view binds the same
// key for "add a row", which wins while its grid has focus.
KeyBinding::new("secondary-shift-i", ImportSqlDump, Some("Session")),
// Giving up on the import dialog: its own `keyboard` handling is off so
// that `enter` cannot dismiss it, which leaves `escape` to this.
KeyBinding::new("escape", CloseImport, Some("ImportDialog")),
// The caret is usually in the editor, whose own context claims keys
// before the session sees them.
KeyBinding::new("secondary-.", CancelQuery, Some("QueryEditor > Input")),
KeyBinding::new("secondary-enter", RunQuery, Some("QueryEditor")),
KeyBinding::new("secondary-t", NewTab, Some("Session")),
KeyBinding::new("secondary-w", CloseTab, Some("Session")),
// Leave the connection without dropping the tab: the workspace swaps a
// fresh manager into it, so another connection can be opened from where
// this one was. Closing the tab outright is the key above.
KeyBinding::new("secondary-d", Disconnect, Some("Session")),
// Open a fresh pool after the server dropped the connection or
// stopped answering, keeping every tab where it is.
KeyBinding::new("secondary-shift-r", Reconnect, Some("Session")),
// Reloads the schema, and the active table's rows; never re-runs a
// query tab's buffer, so it is safe even if that buffer is a write.
KeyBinding::new("secondary-r", Refresh, Some("Session")),
// The console of every statement sent, opened or brought forward.
// The backtick is the terminal/console toggle in several other
// editors, which is the habit this borrows.
KeyBinding::new("secondary-`", OpenConsole, Some("Session")),
// The process list: who is connected to the server and what they're
// running, with a way to end one.
KeyBinding::new("secondary-shift-p", OpenProcessList, Some("Session")),
// The server's own configuration. Shares its key with viewing a
// cell's value, the way Import shares its key with adding a row:
// the more specific context — a grid with the focus — wins there,
// and this is what answers it everywhere else.
KeyBinding::new("secondary-shift-v", OpenServerVariables, Some("Session")),
// The slow/frequent query digest.
KeyBinding::new("secondary-shift-d", OpenQueryDigest, Some("Session")),
// SQLite's housekeeping: integrity checks, optimize, vacuum.
KeyBinding::new("secondary-shift-m", OpenMaintenance, Some("Session")),
// Ctrl+Tab is the tab-switching key on every OS, browsers and macOS
// apps included; Cmd+Shift+] is taken by the connections above.
// Neither is an editor key, so the session sees them from anywhere.
KeyBinding::new("ctrl-tab", NextTab, Some("Session")),
KeyBinding::new("ctrl-shift-tab", PreviousTab, Some("Session")),
KeyBinding::new("ctrl-pagedown", NextTab, Some("Session")),
KeyBinding::new("ctrl-pageup", PreviousTab, Some("Session")),
// Connections are the outer tabs, so they take the shifted keys; the
// workspace wraps every screen, so these work from all of them.
KeyBinding::new("secondary-n", NewConnection, Some("Workspace")),
// On the welcome screen the same key opens the editor dialog instead
// of another tab: the more specific context wins.
KeyBinding::new("secondary-n", NewConnection, Some("Welcome")),
KeyBinding::new("secondary-shift-w", CloseConnection, Some("Workspace")),
KeyBinding::new("secondary-shift-]", NextConnection, Some("Workspace")),
KeyBinding::new("secondary-shift-[", PreviousConnection, Some("Workspace")),
// The editor's own `Input` context leaves these free, so binding them
// at the session — which owns the tabs and their files — is enough.
KeyBinding::new("secondary-o", OpenFile, Some("Session")),
KeyBinding::new("secondary-s", SaveFile, Some("Session")),
KeyBinding::new("secondary-shift-s", SaveFileAs, Some("Session")),
// A table tab has staged edits rather than a file, so the save and
// undo keys apply and discard them. Both are bound on the table view,
// which sits below the session, so they win while it has focus.
KeyBinding::new("secondary-s", ApplyEdits, Some("TableView")),
// A cell editor is an input of its own, so the save key is bound on
// that node too: applying mid-edit folds in what is being typed.
KeyBinding::new(
"secondary-s",
ApplyEdits,
Some("TableView > DataTable > Input"),
),
KeyBinding::new("secondary-z", DiscardEdits, Some("TableView")),
KeyBinding::new("secondary-\\", ToggleRowPanel, Some("TableView")),
// The table binds the arrows, tab, and escape for its own selection
// but leaves `enter` free, so that opens the editor. Both are scoped
// under the table view, leaving query results read-only.
KeyBinding::new("enter", EditCell, Some("TableView > DataTable")),
KeyBinding::new("secondary-shift-n", SetNull, Some("TableView > DataTable")),
// Bound on the grid, like the other row commands: that is the node
// that holds the focus while the user is looking at rows.
KeyBinding::new(
"secondary-shift-i",
InsertRow,
Some("TableView > DataTable"),
),
// Deleting marks the rows rather than writing them away, so the key
// that takes the mark off again sits beside it.
KeyBinding::new(
"secondary-backspace",
DeleteRows,
Some("TableView > DataTable"),
),
KeyBinding::new(
"secondary-shift-backspace",
RestoreRows,
Some("TableView > DataTable"),
),
// Looking at a whole value works on any grid, so these are bound on
// the grid rather than on the table view. So does copying one, picking
// rows out, and everything else about reading a result — a query tab's
// grid answers them exactly as a table tab's does.
KeyBinding::new("secondary-shift-v", ViewCell, Some("DataGrid > DataTable")),
KeyBinding::new("secondary-c", CopyValue, Some("DataGrid > DataTable")),
// Copying a whole line per row, with a header, is the spreadsheet-shaped
// copy; the plain key above remains cell-or-rows without a header.
KeyBinding::new(
"secondary-shift-c",
CopyWithHeaders,
Some("DataGrid > DataTable"),
),
// The table binds the plain arrows for the selection itself; shifted,
// they take the rows they pass over with them.
KeyBinding::new("shift-up", ExtendSelectionUp, Some("DataGrid > DataTable")),
KeyBinding::new(
"shift-down",
ExtendSelectionDown,
Some("DataGrid > DataTable"),
),
// Space is the box beside the focused row, which is not a tab stop:
// with one box per row, tabbing through a page of them to reach the
// grid is worse than a key that picks the row the user is already on.
KeyBinding::new("space", ToggleRow, Some("DataGrid > DataTable")),
// A cell editor's own context is deeper than the table's, so without
// this a space typed into it would pick the row instead of typing the
// character.
KeyBinding::new("space", NoAction, Some("DataGrid > DataTable > Input")),
KeyBinding::new("secondary-a", SelectAllRows, Some("DataGrid > DataTable")),
KeyBinding::new(
"secondary-shift-a",
ClearRowSelection,
Some("DataGrid > DataTable"),
),
// Escape while typing in a cell closes the editor; without this the
// input's own escape, or the table's clear-selection, would take it.
KeyBinding::new("escape", CancelEdit, Some("TableView > DataTable > Input")),
// No context: the settings belong to the app, not to a screen, and the
// handler for it is registered on the app itself.
KeyBinding::new("secondary-,", OpenSettings, None),
// Literally Ctrl on every OS, including macOS, where Cmd+/ is left to
// whatever the focused control does with it.
KeyBinding::new("ctrl-/", ShowShortcuts, None),
// The settings window is not a `Workspace`, so it gets its own close
// keys; Alt+F4 is the Windows/Linux habit and harmless elsewhere.
KeyBinding::new("secondary-w", CloseSettings, Some("SettingsWindow")),
KeyBinding::new("alt-f4", CloseSettings, Some("SettingsWindow")),
KeyBinding::new("escape", CloseSettings, Some("SettingsWindow")),
// The value dialog covers the window while it is open. Enter belongs
// to its text box, which is multi-line, so saving takes the modifier.
KeyBinding::new("escape", CloseValue, Some("ValueDialog")),
KeyBinding::new("secondary-enter", SaveValue, Some("ValueDialog")),
// The caret is in the text box, whose own context binds this key, so
// the dialog's binding has to name that node to win there.
KeyBinding::new("secondary-enter", SaveValue, Some("ValueDialog > Input")),
// The connection editor dialog: Escape gives up, Cmd+Enter connects.
// The fields are single-line inputs, but their own context is deeper
// than the editor's, so both paths are bound. Enter in a field
// connects too, the way it submits any form; it is not bound on the
// editor itself, where it belongs to whichever button has the focus.
KeyBinding::new("escape", EditorClose, Some("ConnectionEditor")),
KeyBinding::new("escape", EditorClose, Some("ConnectionEditor > Input")),
KeyBinding::new("secondary-enter", EditorConnect, Some("ConnectionEditor")),
KeyBinding::new(
"secondary-enter",
EditorConnect,
Some("ConnectionEditor > Input"),
),
KeyBinding::new("enter", EditorConnect, Some("ConnectionEditor > Input")),
]);
}