Файли
openidea/src/platform/navigationModifier.ts
T
l0ng-ai e16253a890 fix(editor): Ctrl/⌘-hover info replaces hover popups instead of stacking (#60)
Root cause: the quick-doc/problem hover and the Ctrl/⌘-hover navigation info were independent tooltips with no precedence, and the navigation modifier was only seen through the focused editor's key events, so holding Ctrl stacked both popups.

Fix: a window-wide platform navigation modifier (`src/platform/navigationModifier.ts`, ⌘ on macOS / Ctrl elsewhere, unchorded); with it held the plain hover doesn't open, late LSP results are dropped, an open hover closes (unless the pointer is inside it, so Ctrl/⌘+C still copies), release clears the link and info.

Siblings: problem/diagnostic hover, debugger value tooltip, inlay hint hovers (no doc or underline through hints, platform-correct Ctrl/⌘-click), consistent ⌘ vs ⌃ handling on macOS.

Verified in the app on macOS (⌘): hover then ⌘ leaves only the nav info, ⌘ before hover shows only nav info, release clears both and plain hover returns. Not verified on Windows.

Fixes #57

https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
2026-10-27 11:46:04 +08:00

78 рядки
2.7 KiB
TypeScript

/**
* The navigation modifier (⌘ on macOS, Ctrl elsewhere) as a live, window-wide
* state: ⌘/Ctrl-hover shows the navigation link and its brief info, and every
* plain hover popup (quick documentation, problems, debugger values) yields to
* it. Tracked on the window so an editor sees the key even when it isn't focused.
*/
import { hasPrimaryModifier, isMacHost } from "./keymap";
type ModifierEvent = Pick<MouseEvent, "metaKey" | "ctrlKey" | "altKey" | "shiftKey">;
/** ⌘/Ctrl alone: no ⌥/⇧ (other gestures) and not the other platform's Ctrl/⌘. */
export function isNavigationGesture(e: ModifierEvent): boolean {
return hasPrimaryModifier(e) && !e.altKey && !e.shiftKey && !(isMacHost() ? e.ctrlKey : e.metaKey);
}
let held = false;
let pointer: Element | null = null;
const listeners = new Set<(held: boolean) => void>();
let installed: (() => void) | null = null;
function setHeld(value: boolean) {
if (held === value) return;
held = value;
for (const fn of [...listeners]) fn(value);
}
function install() {
if (installed || typeof window === "undefined") return;
const onKey = (e: KeyboardEvent) => setHeld(isNavigationGesture(e));
// Also from pointer events: a key released outside the window never reports its keyup.
const onMouse = (e: MouseEvent) => {
pointer = e.target instanceof Element ? e.target : null;
setHeld(isNavigationGesture(e));
};
const onBlur = () => setHeld(false);
const opts = { capture: true, passive: true } as const;
window.addEventListener("keydown", onKey, opts);
window.addEventListener("keyup", onKey, opts);
window.addEventListener("mousemove", onMouse, opts);
window.addEventListener("mousedown", onMouse, opts);
window.addEventListener("blur", onBlur);
installed = () => {
window.removeEventListener("keydown", onKey, opts);
window.removeEventListener("keyup", onKey, opts);
window.removeEventListener("mousemove", onMouse, opts);
window.removeEventListener("mousedown", onMouse, opts);
window.removeEventListener("blur", onBlur);
installed = null;
};
}
/** Whether ⌘ (macOS) / Ctrl is held on its own right now. */
export function navigationModifierHeld(): boolean {
install();
return held;
}
/** Element under the pointer at the last mouse move (null before any). */
export function pointerTarget(): Element | null {
install();
return pointer;
}
/** Called with the new state whenever the navigation modifier is pressed or released. */
export function onNavigationModifier(fn: (held: boolean) => void): () => void {
install();
listeners.add(fn);
return () => listeners.delete(fn);
}
/** Tests: forget the tracked state and listeners. */
export function resetNavigationModifier() {
installed?.();
held = false;
pointer = null;
listeners.clear();
}