fix(editor): restore SQL selection case shortcuts
This commit is contained in:
5 files changed
+109
-3
No files matched your search
@@ -132,6 +132,7 @@ import { createShellLineCommentHighlight } from "@/lib/editor/codemirrorShellLin
|
||||
import { extendQueryEditorSelection, runQueryEditorAltExtendSelection } from "@/lib/editor/queryEditorExtendSelection";
|
||||
import { createQueryEditorStringMouseSelection } from "@/lib/editor/queryEditorStringMouseSelection";
|
||||
import { createQueryEditorCompletionShortcutBindings } from "@/lib/editor/queryEditorCompletionShortcut";
|
||||
import { createQueryEditorSelectionCaseShortcutBindings } from "@/lib/editor/queryEditorSelectionCaseShortcut";
|
||||
import { acceptSelectedCompletionWithRetry, acceptSelectedOrFirstCompletion } from "@/lib/editor/queryEditorCompletionAcceptance";
|
||||
import { createQueryEditorExecutionShortcutBindings, createQueryEditorPostCompositionKeyGuard } from "@/lib/editor/queryEditorExecutionShortcut";
|
||||
import type { StatementExecutionMarker } from "@/lib/tabs/tabPresentation";
|
||||
@@ -1974,8 +1975,8 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
|
||||
...binding(shortcuts.redo, (view) => codeMirrorRedo?.(view) ?? false),
|
||||
...binding(shortcuts.selectAll, (view) => codeMirrorSelectAll?.(view) ?? false),
|
||||
...binding(shortcuts.extendSelection, extendQueryEditorSelectionForView),
|
||||
...binding(shortcuts.uppercaseSelection, () => convertSelectedSqlCase("upper")),
|
||||
...binding(shortcuts.lowercaseSelection, () => convertSelectedSqlCase("lower")),
|
||||
...createQueryEditorSelectionCaseShortcutBindings(shortcuts.uppercaseSelection, () => convertSelectedSqlCase("upper")),
|
||||
...createQueryEditorSelectionCaseShortcutBindings(shortcuts.lowercaseSelection, () => convertSelectedSqlCase("lower")),
|
||||
...binding(shortcuts.toggleLineComment, (view) => codeMirrorToggleLineComment?.(view) ?? false),
|
||||
...binding(shortcuts.toggleBlockComment, (view) => {
|
||||
if (!supportsQueryEditorBlockComments(props.databaseType)) return false;
|
||||
|
||||
@@ -41,6 +41,13 @@ describe("keyboard shortcut matching", () => {
|
||||
expect(eventToShortcut({ key: "+", ctrlKey: true, shiftKey: true }, "Win32")).toBe("Shift+Mod+Plus");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["¨", "KeyU", "Shift+Alt+U"],
|
||||
["Ò", "KeyL", "Shift+Alt+L"],
|
||||
])("records macOS Option-modified %s by physical letter", (key, code, expected) => {
|
||||
expect(eventToShortcut({ key, code, altKey: true, shiftKey: true }, "MacIntel")).toBe(expected);
|
||||
});
|
||||
|
||||
it("keeps Control distinct from Command when recording macOS shortcuts", () => {
|
||||
const controlShortcut = eventToShortcut({ key: "b", ctrlKey: true }, "MacIntel");
|
||||
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { EditorState, EditorSelection } from "@codemirror/state";
|
||||
import { EditorView, keymap, runScopeHandlers } from "@codemirror/view";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createQueryEditorSelectionCaseShortcutBindings } from "@/lib/editor/queryEditorSelectionCaseShortcut";
|
||||
|
||||
function createView(shortcut: string, run: (view: EditorView) => boolean, platform: string): EditorView {
|
||||
return new EditorView({
|
||||
parent: document.createElement("div"),
|
||||
state: EditorState.create({
|
||||
doc: "select MixedCase",
|
||||
selection: EditorSelection.range(0, 16),
|
||||
extensions: [keymap.of(createQueryEditorSelectionCaseShortcutBindings(shortcut, run, platform))],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
describe("QueryEditor selection case shortcuts", () => {
|
||||
it.each([
|
||||
["Shift+Alt+U", "U"],
|
||||
["Shift+Alt+L", "L"],
|
||||
])("matches macOS %s by physical key when Option transforms event.key", (shortcut, code) => {
|
||||
const run = vi.fn(() => true);
|
||||
const view = createView(shortcut, run, "MacIntel");
|
||||
const event = new KeyboardEvent("keydown", {
|
||||
key: code === "U" ? "¨" : "Ò",
|
||||
code: `Key${code}`,
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
expect(runScopeHandlers(view, event, "editor")).toBe(true);
|
||||
expect(run).toHaveBeenCalledOnce();
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
it("leaves the shortcut unhandled when the conversion command declines", () => {
|
||||
const run = vi.fn(() => false);
|
||||
const view = createView("Shift+Alt+U", run, "MacIntel");
|
||||
const event = new KeyboardEvent("keydown", {
|
||||
key: "¨",
|
||||
code: "KeyU",
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
expect(runScopeHandlers(view, event, "editor")).toBe(false);
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
expect(run).toHaveBeenCalledOnce();
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
it("keeps multi-stroke custom shortcuts on CodeMirror's sequence binding", () => {
|
||||
const run = vi.fn(() => true);
|
||||
expect(createQueryEditorSelectionCaseShortcutBindings("Mod+K Mod+U", run, "MacIntel")).toEqual([
|
||||
{
|
||||
key: "Mod-k Mod-u",
|
||||
preventDefault: true,
|
||||
run,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -37,10 +37,17 @@ function shortcutKeyName(key: string): string | null {
|
||||
return key;
|
||||
}
|
||||
|
||||
function shortcutKeyNameFromEvent(event: ShortcutLikeEvent, platform: string): string | null {
|
||||
if (event.altKey && isMacShortcutPlatform(platform) && /^Key[A-Z]$/.test(event.code ?? "")) {
|
||||
return event.code!.slice(3);
|
||||
}
|
||||
return shortcutKeyName(event.key);
|
||||
}
|
||||
|
||||
export function eventToShortcut(event: ShortcutLikeEvent, platform = globalThis.navigator?.platform || ""): string | null {
|
||||
if (event.isComposing) return null;
|
||||
|
||||
const key = shortcutKeyName(event.key);
|
||||
const key = shortcutKeyNameFromEvent(event, platform);
|
||||
if (!key) return null;
|
||||
|
||||
const hasModifier = !!event.metaKey || !!event.ctrlKey || !!event.altKey || !!event.shiftKey;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Command, KeyBinding } from "@codemirror/view";
|
||||
import { matchesShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { parseShortcutStrokes } from "@/lib/editor/shortcutDisplay";
|
||||
import { shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
|
||||
export function createQueryEditorSelectionCaseShortcutBindings(shortcut: string, run: Command, platform = globalThis.navigator?.platform || ""): KeyBinding[] {
|
||||
if (!shortcut) return [];
|
||||
|
||||
// CodeMirror matches event.key, but macOS Option+letter produces a composed
|
||||
// character. Use the shared physical-key fallback for single-stroke bindings.
|
||||
if (parseShortcutStrokes(shortcut).length === 1) {
|
||||
return [
|
||||
{
|
||||
any(view, event) {
|
||||
if (!matchesShortcut(event, shortcut, platform)) return false;
|
||||
return run(view);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [{ key: shortcutToCodeMirrorKey(shortcut), preventDefault: true, run }];
|
||||
}
|
||||
Reference in new issue
Block a user