From 8379f0d0488496e7883ece56adb8b8afde8a02c3 Mon Sep 17 00:00:00 2001 From: zipg Date: Sun, 2 Aug 2026 10:08:48 +0800 Subject: [PATCH] fix(editor): preserve multi-cursor clicks on identifiers --- .../src/components/editor/QueryEditor.vue | 15 ++++++++------- .../editor/queryEditorPointerSelection.spec.ts | 16 +++++++++++++++- .../lib/editor/queryEditorPointerSelection.ts | 10 ++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 4823e18603..90d6c8388f 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -87,7 +87,7 @@ import { createInsertValueHintsExtension, requestInsertValueHintsRefresh } from import { focusEditorView } from "@/lib/editor/queryEditorFocus"; import { createDbxCodeMirrorSqlDialect, type CodeMirrorSqlDialectName } from "@/lib/editor/codemirrorSqlDialect"; import { sqlSemanticTableNameSpansForSyntaxTree } from "@/lib/editor/codemirrorSqlSemanticHighlight"; -import { startsQueryEditorRectangularSelection } from "@/lib/editor/queryEditorPointerSelection"; +import { startsQueryEditorRectangularSelection, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; import { LARGE_PASTE_HISTORY_USER_EVENT, normalizeQueryEditorPasteText, recoverableNativePasteSuffix, shouldRecoverLargeTauriPaste } from "@/lib/editor/queryEditorLargePaste"; import type { StatementExecutionMarker } from "@/lib/tabs/tabPresentation"; import { isSchemaAware, isSingleDatabase, supportsDatabaseNameCompletion, supportsDatabaseSchemaQualifier, supportsSqlInListPaste } from "@/lib/database/databaseFeatureSupport"; @@ -845,7 +845,7 @@ function tableNavigationIdentifierAt(currentView: EditorViewType, event: MouseEv } function updateTableNavigationHover(currentView: EditorViewType, event: MouseEvent) { - if (!event.metaKey && !event.ctrlKey) { + if (!usesQueryEditorObjectNavigationModifier(event)) { clearTableNavigationHover(); return false; } @@ -855,7 +855,7 @@ function updateTableNavigationHover(currentView: EditorViewType, event: MouseEve } function clearTableNavigationHoverOnModifierRelease(event: KeyboardEvent) { - if (!event.metaKey && !event.ctrlKey) clearTableNavigationHover(); + if (!usesQueryEditorObjectNavigationModifier(event)) clearTableNavigationHover(); } function isEditorScrollbarPointerEvent(currentView: EditorViewType, event: MouseEvent) { @@ -4219,14 +4219,15 @@ onMounted(async () => { if (currentView && startEditorSelectionDrag(currentView, event)) { return true; } - // Click without modifier -> close column panel - if (!event.metaKey && !event.ctrlKey) { - if (event.button === 0) { + // Alt belongs to CodeMirror's rectangular and multi-cursor gestures, + // even when Cmd/Ctrl is held at the same time. + if (!usesQueryEditorObjectNavigationModifier(event)) { + // Click without modifier -> close column panel + if (!event.metaKey && !event.ctrlKey && event.button === 0) { emit("closeColumnPanel"); } return false; } - // Only handle Ctrl/Cmd + left click if (event.button !== 0) return false; if (!currentView || !props.connectionId || props.database == null) { diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorPointerSelection.spec.ts index da263ec3f3..97f654f617 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 } from "@/lib/editor/queryEditorPointerSelection"; +import { startsQueryEditorRectangularSelection, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection"; describe("query editor pointer selection", () => { it("starts rectangular selection for Alt+left drag", () => { @@ -13,4 +13,18 @@ describe("query editor pointer selection", () => { it("leaves ordinary left clicks to the normal cursor handler", () => { expect(startsQueryEditorRectangularSelection({ altKey: false, button: 0 })).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); + }); + + it("leaves Alt+Cmd and Alt+Ctrl to multi-cursor selection", () => { + expect(usesQueryEditorObjectNavigationModifier({ altKey: true, ctrlKey: false, metaKey: true })).toBe(false); + expect(usesQueryEditorObjectNavigationModifier({ altKey: true, ctrlKey: true, metaKey: false })).toBe(false); + }); + + it("does not treat an unmodified pointer event as object navigation", () => { + expect(usesQueryEditorObjectNavigationModifier({ altKey: false, ctrlKey: false, metaKey: false })).toBe(false); + }); }); diff --git a/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts b/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts index bcc545f909..5a12e9ccbc 100644 --- a/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts +++ b/apps/desktop/src/lib/editor/queryEditorPointerSelection.ts @@ -3,6 +3,16 @@ export interface QueryEditorPointerEvent { button: number; } +export interface QueryEditorObjectNavigationModifierEvent { + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; +} + export function startsQueryEditorRectangularSelection(event: QueryEditorPointerEvent): boolean { return event.altKey || event.button === 1; } + +export function usesQueryEditorObjectNavigationModifier(event: QueryEditorObjectNavigationModifierEvent): boolean { + return !event.altKey && (event.metaKey || event.ctrlKey); +}