mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
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
78 lines
2.7 KiB
TypeScript
78 lines
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();
|
|
}
|