feat(editor): add explain plan shortcut
This commit is contained in:
6 files changed
+53
-2
No files matched your search
@@ -204,6 +204,8 @@ const props = defineProps<{
|
||||
autoFocus?: boolean;
|
||||
forceWordWrap?: boolean;
|
||||
hideExecutionControls?: boolean;
|
||||
enableExplainShortcut?: boolean;
|
||||
canExplain?: boolean;
|
||||
initialViewport?: { scrollTop: number; scrollLeft: number };
|
||||
initialSelection?: { anchor: number; head: number };
|
||||
statementExecutionMarkers?: StatementExecutionMarker[];
|
||||
@@ -235,6 +237,7 @@ const emit = defineEmits<{
|
||||
formatError: [message: string];
|
||||
execute: [source: SqlExecutionOverride];
|
||||
executeInNewResultTab: [source: SqlExecutionOverride];
|
||||
explain: [];
|
||||
exportQuery: [payload: { sql: string; format: "csv" | "xlsx" | "txt"; columnComments?: (string | null)[] }];
|
||||
save: [];
|
||||
clickTable: [target: SqlObjectNavigationTarget];
|
||||
@@ -2173,6 +2176,17 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
|
||||
(currentView) => shouldBlockExecutionShortcut(undefined, currentView),
|
||||
);
|
||||
const executeInNewResultTabBindings = props.hideExecutionControls ? [] : createQueryEditorExecutionShortcutBindings(shortcuts.executeSqlInNewResultTab, requestExecuteInNewResultTab, (currentView) => shouldBlockExecutionShortcut(undefined, currentView));
|
||||
const explainBindings = props.enableExplainShortcut
|
||||
? createQueryEditorExecutionShortcutBindings(
|
||||
shortcuts.explainSql,
|
||||
() => {
|
||||
emit("explain");
|
||||
return true;
|
||||
},
|
||||
(currentView) => shouldBlockExecutionShortcut(undefined, currentView),
|
||||
() => !!props.canExplain,
|
||||
)
|
||||
: [];
|
||||
const replaceShortcutBindings = createQueryEditorReplaceShortcutBindings(shortcuts.replace, openReplace);
|
||||
const replaceShortcutHandler = createQueryEditorReplaceShortcutHandler({
|
||||
shortcut: shortcuts.replace,
|
||||
@@ -2221,6 +2235,7 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
|
||||
...replaceShortcutBindings,
|
||||
...executeInNewResultTabBindings,
|
||||
...executeBindings,
|
||||
...explainBindings,
|
||||
...binding(shortcuts.saveSql, () => {
|
||||
emit("save");
|
||||
return true;
|
||||
|
||||
@@ -1298,6 +1298,8 @@ defineExpose({
|
||||
:initial-viewport="activeTab.editorViewport"
|
||||
:initial-selection="activeTab.editorSelection"
|
||||
:force-word-wrap="activeTab.forceWordWrap"
|
||||
enable-explain-shortcut
|
||||
:can-explain="!activeTab.isExecuting && !activeTab.isExplaining && !!executableSql.trim()"
|
||||
@update:model-value="emit('editorUpdate', activeTab.id, $event)"
|
||||
@selection-change="emit('editorSelectionChange', activeTab.id, $event)"
|
||||
@send-selection-to-ai="emit('sendSelectionToAi', activeTab.id, $event)"
|
||||
@@ -1309,6 +1311,7 @@ defineExpose({
|
||||
@format-error="emit('formatError', activeTab.id)"
|
||||
@execute="emit('execute', activeTab.id, $event)"
|
||||
@execute-in-new-result-tab="emit('executeInNewResultTab', activeTab.id, $event)"
|
||||
@explain="emit('explain', activeTab.id)"
|
||||
@export-query="handleExportQuery"
|
||||
@save="emit('saveSql', props.activeTab.id)"
|
||||
@click-table="onHandleClickTable"
|
||||
|
||||
@@ -39,6 +39,24 @@ describe("QueryEditor execution shortcuts", () => {
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
it("checks shortcut eligibility at dispatch time without rebuilding the keymap", () => {
|
||||
let enabled = false;
|
||||
const explain = vi.fn(() => true);
|
||||
const view = createView([createQueryEditorExecutionShortcutBindings("Mod+E", explain, isComposing, () => enabled)]);
|
||||
|
||||
expect(runScopeHandlers(view, executeEvent("e"), "editor")).toBe(true);
|
||||
expect(explain).not.toHaveBeenCalled();
|
||||
|
||||
enabled = true;
|
||||
expect(runScopeHandlers(view, executeEvent("e"), "editor")).toBe(true);
|
||||
expect(explain).toHaveBeenCalledOnce();
|
||||
|
||||
enabled = false;
|
||||
expect(runScopeHandlers(view, executeEvent("e"), "editor")).toBe(true);
|
||||
expect(explain).toHaveBeenCalledOnce();
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
it("consumes the SQL shortcut without executing during IME composition", () => {
|
||||
const execute = vi.fn(() => true);
|
||||
const view = createView([createQueryEditorExecutionShortcutBindings("Mod+Enter", execute, isComposing)]);
|
||||
|
||||
@@ -152,6 +152,14 @@ describe("shortcutRegistry editor actions", () => {
|
||||
expect(findShortcutConflict("expandSelectStar", DEFAULT_SHORTCUT_SETTINGS.expandSelectStar, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
|
||||
});
|
||||
|
||||
it("registers a configurable editor shortcut for the explain plan", () => {
|
||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "explainSql");
|
||||
|
||||
expect(definition).toMatchObject({ labelKey: "toolbar.explainPlan", scope: "editor", defaultShortcut: "Mod+E" });
|
||||
expect(shortcutToCodeMirrorKey(DEFAULT_SHORTCUT_SETTINGS.explainSql)).toBe("Mod-e");
|
||||
expect(findShortcutConflict("explainSql", "Mod+E", DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps current-view search and editor find contextual on Mod+F", () => {
|
||||
const focusSearch = SHORTCUT_DEFINITIONS.find((item) => item.id === "focusSearch");
|
||||
const find = SHORTCUT_DEFINITIONS.find((item) => item.id === "find");
|
||||
|
||||
@@ -59,14 +59,14 @@ export function createQueryEditorPostCompositionKeyGuard(options: { navigator?:
|
||||
* document, so the app-level shortcut fallback also checks the editor state.
|
||||
* This guard covers the keymap path when CodeMirror still dispatches it.
|
||||
*/
|
||||
export function createQueryEditorExecutionShortcutBindings(shortcut: string, run: Command, isComposing: (view: EditorView) => boolean): KeyBinding[] {
|
||||
export function createQueryEditorExecutionShortcutBindings(shortcut: string, run: Command, isComposing: (view: EditorView) => boolean, isEnabled: () => boolean = () => true): KeyBinding[] {
|
||||
if (!shortcut) return [];
|
||||
return [
|
||||
{
|
||||
key: shortcutToCodeMirrorKey(shortcut),
|
||||
preventDefault: true,
|
||||
run(view) {
|
||||
if (isComposing(view)) return true;
|
||||
if (isComposing(view) || !isEnabled()) return true;
|
||||
return run(view);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isMacShortcutPlatform, parseShortcutStrokes, shortcutDisplayParts } fro
|
||||
export type ShortcutActionId =
|
||||
| "executeSql"
|
||||
| "executeSqlInNewResultTab"
|
||||
| "explainSql"
|
||||
| "formatSql"
|
||||
| "expandSelectStar"
|
||||
| "toggleLineComment"
|
||||
@@ -151,6 +152,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||
scope: "editor",
|
||||
defaultShortcut: "Mod+\\",
|
||||
},
|
||||
{
|
||||
id: "explainSql",
|
||||
labelKey: "toolbar.explainPlan",
|
||||
scope: "editor",
|
||||
defaultShortcut: "Mod+E",
|
||||
},
|
||||
{
|
||||
id: "formatSql",
|
||||
labelKey: "settings.shortcutFormatSql",
|
||||
|
||||
Reference in new issue
Block a user