import { Button } from "@/components/ui/button"; import { LoadingButton } from "@/components/ui/loading-button"; import { Separator } from "@/components/ui/separator"; import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react"; import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; import { Loader2 } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { SymbolDefinition, useHoveredOverSymbolInfo } from "./useHoveredOverSymbolInfo"; import { SymbolDefinitionPreview } from "./symbolDefinitionPreview"; interface SymbolHoverPopupProps { editorRef: ReactCodeMirrorRef; language: string; revisionName: string; onFindReferences: (symbolName: string) => void; onGotoDefinition: (symbolName: string, symbolDefinitions: SymbolDefinition[]) => void; } export const SymbolHoverPopup: React.FC = ({ editorRef, revisionName, language, onFindReferences, onGotoDefinition: _onGotoDefinition, }) => { const ref = useRef(null); const [isSticky, setIsSticky] = useState(false); const symbolInfo = useHoveredOverSymbolInfo({ editorRef, isSticky, revisionName, language, }); // Positions the popup relative to the symbol useEffect(() => { if (!symbolInfo) { return; } const virtualElement: VirtualElement = { getBoundingClientRect: () => { return symbolInfo.element.getBoundingClientRect(); } } if (ref.current) { computePosition(virtualElement, ref.current, { placement: 'top', middleware: [ offset(2), flip({ mainAxis: true, crossAxis: false, fallbackPlacements: ['bottom'], boundary: editorRef.view?.dom, }), shift({ padding: 5, }) ] }).then(({ x, y }) => { if (ref.current) { ref.current.style.left = `${x}px`; ref.current.style.top = `${y}px`; } }) } }, [symbolInfo, editorRef]); const onGotoDefinition = useCallback(() => { if (!symbolInfo || !symbolInfo.symbolDefinitions) { return; } _onGotoDefinition(symbolInfo.symbolName, symbolInfo.symbolDefinitions); }, [symbolInfo, _onGotoDefinition]); // @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held // down to navigate to the definition. We should also only show the underline when the key // is held, hover is active, and we have found the symbol definition. useEffect(() => { if (!symbolInfo || !symbolInfo.symbolDefinitions) { return; } symbolInfo.element.addEventListener("click", onGotoDefinition); return () => { symbolInfo.element.removeEventListener("click", onGotoDefinition); } }, [symbolInfo, onGotoDefinition]); return symbolInfo ? (
setIsSticky(true)} onMouseOut={() => setIsSticky(false)} > {symbolInfo.isSymbolDefinitionsLoading ? (
Loading...
) : symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 0 ? ( ) : (

No hover info found

)}
{ !symbolInfo.isSymbolDefinitionsLoading && (!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) ? "No definition found" : `Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}` }
) : null; };