diff --git a/src/editor/lsp/navigation.ts b/src/editor/lsp/navigation.ts index 9dc2d5cd..7afdda09 100644 --- a/src/editor/lsp/navigation.ts +++ b/src/editor/lsp/navigation.ts @@ -1,8 +1,8 @@ /** - * Go to declaration / implementation / type declaration (⌘B, ⌥⌘B, ⇧⌘B and - * ⌘-click with a link underline while ⌘ is held), the multiple-targets - * chooser, and document highlights (occurrences of the symbol at the caret, - * plus the sticky find.highlightUsages variant). + * Go to declaration / implementation / type declaration (⌘B, ⌥⌘B, ⇧⌘B, + * ⌘-click with a link underline while ⌘ is held, and a plain middle-button + * click), the multiple-targets chooser, and document highlights (occurrences + * of the symbol at the caret, plus the sticky find.highlightUsages variant). */ import { EditorSelection, Prec, StateEffect, StateField, type Extension, type Text } from "@codemirror/state"; import { @@ -489,7 +489,7 @@ const linkPlugin = ViewPlugin.fromClass( this.show(false); }, mousedown(e, view) { - if (e.button !== 0) return false; + if (e.button !== 0 && e.button !== 1) return false; if ( dispatchMouseShortcut(e, () => { const pos = view.posAtCoords({ x: e.clientX, y: e.clientY }, false); @@ -502,7 +502,8 @@ const linkPlugin = ViewPlugin.fromClass( e.preventDefault(); return true; } - if (!isNavigationGesture(e)) return false; + // IDEA parity: middle button navigates on a plain click, without the modifier. + if (e.button === 1 ? e.metaKey || e.ctrlKey || e.altKey || e.shiftKey : !isNavigationGesture(e)) return false; const pos = view.posAtCoords({ x: e.clientX, y: e.clientY }, false); if (pos == null) return false; e.preventDefault(); diff --git a/src/editor/lsp/navigationMouse.test.ts b/src/editor/lsp/navigationMouse.test.ts index 3940c61d..5ec3b9d5 100644 --- a/src/editor/lsp/navigationMouse.test.ts +++ b/src/editor/lsp/navigationMouse.test.ts @@ -94,6 +94,14 @@ describe("mouse navigation", () => { expect(lspGoto).toHaveBeenCalledExactlyOnceWith(1, { line: 0, col: 9 }, "definition"); }); + it("opens the definition from a plain middle-button click", async () => { + const event = click({ button: 1 }); + await vi.waitFor(() => expect(openFile).toHaveBeenCalledOnce()); + expect(lspGoto).toHaveBeenCalledExactlyOnceWith(1, { line: 0, col: 9 }, "definition"); + expect(view.state.selection.main.head).toBe(9); + expect(event.defaultPrevented).toBe(true); + }); + it.each([ { button: 1, metaKey: true, altKey: true }, { button: 2, metaKey: true, altKey: true },