From a688aadec2e055a8ab4ca94f916890e8981c4e7e Mon Sep 17 00:00:00 2001 From: Eve <92513238+Eve-z1@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:30:23 +0800 Subject: [PATCH] fix(editor): preserve multi-click selection drags --- apps/desktop/src/components/editor/QueryEditor.vue | 7 ++----- .../editor/__tests__/QueryEditorContextMenu.spec.ts | 13 ------------- .../editor/queryEditorPointerSelection.spec.ts | 10 +++++++++- .../src/lib/editor/queryEditorPointerSelection.ts | 9 +++++++++ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 2b9f93e4dc..917015447e 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -137,7 +137,7 @@ import { sqlBlockFoldService } from "@/lib/editor/codemirrorSqlBlockFolding"; import { focusEditorView } from "@/lib/editor/queryEditorFocus"; import { createDbxCodeMirrorSqlDialect, type CodeMirrorSqlDialectName } from "@/lib/editor/codemirrorSqlDialect"; import { sqlSemanticTableNameSpansForSyntaxTree } from "@/lib/editor/codemirrorSqlSemanticHighlight"; -import { startsQueryEditorRectangularSelection, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; +import { startsQueryEditorRectangularSelection, startsQueryEditorSelectionDrag, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; import { LARGE_PASTE_HISTORY_USER_EVENT, normalizeQueryEditorPasteText, recoverableNativePasteSuffix, shouldRecoverLargeTauriPaste } from "@/lib/editor/queryEditorLargePaste"; import { computePasteCaretResyncTarget } from "@/lib/editor/queryEditorPasteCaretResync"; import { queryEditorCommentTokens, queryEditorLineCommentToken, queryEditorWordLanguageData } from "@/lib/editor/queryEditorLineComment"; @@ -1417,10 +1417,7 @@ function updateEditorSelectionDropCursor(currentView: EditorViewType, event: Mou } function startEditorSelectionDrag(currentView: EditorViewType, event: MouseEvent): boolean { - // Shift is CodeMirror's native extend-selection gesture. Keep it out of the - // custom selection drag path so a shift-click inside the current selection - // extends or shrinks the selection instead of collapsing it to the cursor. - if (event.shiftKey) return false; + if (!startsQueryEditorSelectionDrag(event)) return false; const selection = selectedRangeAtPointer(currentView, event); if (!selection) return false; diff --git a/apps/desktop/src/components/editor/__tests__/QueryEditorContextMenu.spec.ts b/apps/desktop/src/components/editor/__tests__/QueryEditorContextMenu.spec.ts index ad18889a36..9271d9f347 100644 --- a/apps/desktop/src/components/editor/__tests__/QueryEditorContextMenu.spec.ts +++ b/apps/desktop/src/components/editor/__tests__/QueryEditorContextMenu.spec.ts @@ -199,16 +199,3 @@ describe("QueryEditor batch column selection", () => { expect(source).toContain("maxRenderedOptions: batchColumnSelectionExpandedRendering ? Number.MAX_SAFE_INTEGER : 100,"); }); }); - -describe("QueryEditor pointer selection", () => { - it("lets CodeMirror handle shift-click instead of starting the selection drag gesture", () => { - const start = source.indexOf("function startEditorSelectionDrag"); - const end = source.indexOf("\n}\n\nfunction executeFromContextMenu", start); - const dragSource = source.slice(start, end); - - expect(start).toBeGreaterThanOrEqual(0); - expect(end).toBeGreaterThan(start); - expect(dragSource).toContain("if (event.shiftKey) return false;"); - expect(dragSource.indexOf("if (event.shiftKey) return false;")).toBeLessThan(dragSource.indexOf("const selection = selectedRangeAtPointer")); - }); -}); diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts index 97f654f617..be7ce89d60 100644 --- a/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts +++ b/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { startsQueryEditorRectangularSelection, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; +import { startsQueryEditorRectangularSelection, startsQueryEditorSelectionDrag, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; describe("query editor pointer selection", () => { it("starts rectangular selection for Alt+left drag", () => { @@ -14,6 +14,14 @@ describe("query editor pointer selection", () => { expect(startsQueryEditorRectangularSelection({ altKey: false, button: 0 })).toBe(false); }); + it("leaves multi-click and Shift selection gestures to CodeMirror", () => { + expect(startsQueryEditorSelectionDrag({ detail: 0, shiftKey: false })).toBe(true); + expect(startsQueryEditorSelectionDrag({ detail: 1, shiftKey: false })).toBe(true); + expect(startsQueryEditorSelectionDrag({ detail: 1, shiftKey: true })).toBe(false); + expect(startsQueryEditorSelectionDrag({ detail: 2, shiftKey: false })).toBe(false); + expect(startsQueryEditorSelectionDrag({ detail: 3, shiftKey: false })).toBe(false); + }); + it("uses Cmd or Ctrl without Alt for object navigation", () => { expect(usesQueryEditorObjectNavigationModifier({ altKey: false, ctrlKey: false, metaKey: true })).toBe(true); expect(usesQueryEditorObjectNavigationModifier({ altKey: false, ctrlKey: true, metaKey: false })).toBe(true); diff --git a/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts b/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts index 5a12e9ccbc..4b234a98f1 100644 --- a/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts +++ b/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts @@ -3,6 +3,11 @@ export interface QueryEditorPointerEvent { button: number; } +export interface QueryEditorSelectionDragEvent { + detail: number; + shiftKey: boolean; +} + export interface QueryEditorObjectNavigationModifierEvent { altKey: boolean; ctrlKey: boolean; @@ -13,6 +18,10 @@ export function startsQueryEditorRectangularSelection(event: QueryEditorPointerE return event.altKey || event.button === 1; } +export function startsQueryEditorSelectionDrag(event: QueryEditorSelectionDragEvent): boolean { + return !event.shiftKey && event.detail <= 1; +} + export function usesQueryEditorObjectNavigationModifier(event: QueryEditorObjectNavigationModifierEvent): boolean { return !event.altKey && (event.metaKey || event.ctrlKey); }