fix(editor): respect custom Ctrl+D shortcuts

This commit is contained in:
jearhe authored and GitHub committed 2026-08-16 11:24:06 +08:00
1 parent c63c517b4f
commit edb136d960
4 files changed
+69 -2

No files matched your search

@@ -103,6 +103,7 @@ import type { SqlHighlighter } from "@/lib/sql/sqlHighlighter";
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
import { createQueryEditorSearchKeymap } from "@/lib/editor/queryEditorSearchKeymap";
import { searchKeymapWithoutModD } from "@/lib/editor/codemirrorSearchKeymap";
import { appendSqlCompletionSpace } from "@/lib/editor/sqlCompletionInsertion";
import { compareSqlCompletions, completionLabelPresentation } from "@/lib/editor/sqlCompletionPresentation";
import { clampEditorFontSize, createEditorZoomCommitScheduler, fontSizeFromGestureScale, fontSizeFromWheelDelta } from "@/lib/editor/editorZoom";
@@ -4855,7 +4856,7 @@ onMounted(async () => {
activeLineHighlighter,
// Vim must be mounted before DBX/default keymaps so normal-mode keys are handled first.
vimModeComp.of(vimModeExtension(initialSettings.vimModeEnabled)),
keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap]),
keymap.of([...defaultKeymap, ...searchKeymapWithoutModD(searchKeymap), ...historyKeymap, ...foldKeymap, ...completionKeymap]),
sqlLanguageComp.of(buildSqlLanguageExtension()),
sqlSemanticHighlightComp.of(buildSqlSemanticHighlightExtension()),
tooltips({ parent: tooltipParent }),
@@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useToast } from "@/composables/useToast";
import { copyToClipboard } from "@/lib/common/clipboard";
import { saveTextFile } from "@/lib/export/saveTextFile";
import { searchKeymapWithoutModD } from "@/lib/editor/codemirrorSearchKeymap";
import {
DEFAULT_SQL_FORMATTER_SETTINGS,
SQL_FORMATTER_CONFIG_FORMATTER,
@@ -361,7 +362,7 @@ function jsonEditorKeymapExtension(modules: CodeMirrorModules) {
const { keymap } = modules.view;
const commands = modules.commands;
const search = modules.search;
return keymap.of([...search.searchKeymap, ...commands.historyKeymap, ...commands.defaultKeymap]);
return keymap.of([...searchKeymapWithoutModD(search.searchKeymap), ...commands.historyKeymap, ...commands.defaultKeymap]);
}
async function initJsonEditor() {
@@ -0,0 +1,48 @@
// @vitest-environment happy-dom
import { EditorState } from "@codemirror/state";
import { EditorView, keymap, runScopeHandlers } from "@codemirror/view";
import { searchKeymap } from "@codemirror/search";
import { describe, expect, it } from "vitest";
import { searchKeymapWithoutModD } from "@/lib/editor/codemirrorSearchKeymap";
describe("searchKeymapWithoutModD", () => {
it("drops the built-in Mod-d binding from the search keymap", () => {
const filtered = searchKeymapWithoutModD(searchKeymap);
expect(filtered.find((binding) => binding.key === "Mod-d")).toBeUndefined();
});
it("keeps the other search bindings intact", () => {
const filtered = searchKeymapWithoutModD(searchKeymap);
const keys = filtered.map((binding) => binding.key);
expect(keys).toContain("Mod-f");
expect(keys).toContain("Mod-g");
expect(keys).toContain("Mod-Alt-g");
expect(keys).toContain("Mod-Shift-l");
expect(keys).toContain("F3");
});
it("baseline: the stock searchKeymap consumes Ctrl+D (the bug being fixed)", () => {
const view = new EditorView({
parent: document.createElement("div"),
state: EditorState.create({
extensions: [keymap.of([...searchKeymap])],
}),
});
expect(runScopeHandlers(view, new KeyboardEvent("keydown", { key: "d", ctrlKey: true }), "editor")).toBe(true);
view.destroy();
});
it("no longer consumes Ctrl+D, letting the event bubble to the global shortcut handler", () => {
const view = new EditorView({
parent: document.createElement("div"),
state: EditorState.create({
extensions: [keymap.of([...searchKeymapWithoutModD(searchKeymap)])],
}),
});
expect(runScopeHandlers(view, new KeyboardEvent("keydown", { key: "d", ctrlKey: true }), "editor")).toBe(false);
view.destroy();
});
});
@@ -0,0 +1,17 @@
import type { KeyBinding } from "@codemirror/view";
/**
* `@codemirror/search` 的 `searchKeymap` 内置硬编码了 `Mod-d` = `selectNextOccurrence`
* (多光标选择下一个匹配词),与 DBX 的可配置快捷键体系冲突:
*
* - DBX 中 `Mod+D` 默认绑定「复制行 / 复制当前行」等用户可配置动作(见 shortcutRegistry.ts);
* 一旦用户把 `Mod+D` 配置给其他动作(例如把「新建查询」改为 Ctrl+D),searchKeymap 的
* `Mod-d` 会在编辑器中抢先匹配并 `preventDefault`,事件冒泡到 window 时
* `defaultPrevented=true`,App.vue 的全局 handleKeydown 直接返回,用户配置的快捷键永不触发。
*
* 这里移除 `Mod-d` 绑定,保证用户可配置的快捷键优先于 CodeMirror 内置硬编码键位
* (与 #4544 修复 Mod+F / Mod+H 冲突的思路一致)。
*/
export function searchKeymapWithoutModD(searchKeymap: readonly KeyBinding[]): KeyBinding[] {
return searchKeymap.filter((binding) => binding.key !== "Mod-d");
}