fix(editor): restore SQL selection case shortcuts

This commit is contained in:
zipg authored and GitHub committed 2026-08-26 00:27:27 +08:00
1 parent 2a2357e2f1
commit 771a7f03a0
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");
@@ -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 }];
}