import type { ReactElement } from 'react' import type { BlockTreeState } from './BlockTree' import type { ShownPeek } from './definitionPeek' import type { DefinitionPeek } from './types' import { useLayoutEffect, useMemo, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { BlockLevel } from './BlockTree' import { PEEK_ATTRIBUTE, placePeek } from './definitionPeek' import { DisplayNameContext, nameMap } from './displayNames' import { MarkdownKindContext } from './TextPreview' import { HighlightContext } from './useOccurrences' import { collapsedIds } from './viewState' const NONE: ReadonlySet = new Set() const NOT_COVERED: ReadonlyMap = new Map() /** * 浮窗里的那张卡片:只读的块树(不编辑、不问 AI、不重写注释、不标差异、不筛选),折叠展开只记在浮窗里,默认同页面(类、函数收起子块)。 * 名字的下标是那个文件的:中文名换成卡片带来的,不画页面上的高亮;Markdown 不认不带路径的引用。 */ export function PeekTree({ peek }: { peek: DefinitionPeek }): ReactElement { const [folds, setFolds] = useState>({}) const collapsed = useMemo(() => collapsedIds([peek.block], folds), [peek, folds]) const names = useMemo(() => nameMap(peek.names), [peek]) const tree: BlockTreeState = { collapsed, hidden: NONE, temporary: NONE, covered: NOT_COVERED, kindName: kind => kind, onToggleFold: id => setFolds(prev => ({ ...prev, [id]: !collapsed.has(id) })), onSetFolds: next => setFolds(prev => ({ ...prev, ...next })), // 不标差异就没有「只看改动」折起来的块 onOpenRun: () => {}, } return ( ) } /** * 定义卡片浮窗:标题一行「相对路径:行号」,点了跳到定义(同 ⌘ / Ctrl + 点击);下面是卡片,太大时在浮窗里滚。 * 外框用 VS Code 悬停框的颜色;portal 到 body,先在左上角量出大小,再落到名字下方或上方(placePeek)。 */ export function DefinitionPeekPopup({ shown, onJump }: { shown: ShownPeek | undefined, onJump: () => void }): ReactElement | null { const popupRef = useRef(null) useLayoutEffect(() => { const popup = popupRef.current if (!shown || !popup) return Object.assign(popup.style, { top: '0px', left: '0px', bottom: '', maxWidth: '', maxHeight: '', visibility: 'hidden' }) const place = placePeek(shown.anchor.getBoundingClientRect(), popup.getBoundingClientRect(), { width: window.innerWidth, height: window.innerHeight }) Object.assign(popup.style, { top: 'top' in place ? `${place.top}px` : '', bottom: 'bottom' in place ? `${place.bottom}px` : '', left: `${place.left}px`, maxWidth: `${place.maxWidth}px`, maxHeight: `${place.maxHeight}px`, visibility: 'visible', }) }, [shown]) if (!shown) return null const { peek } = shown const where = `${peek.path}:${peek.line + 1}` return createPortal(
, document.body, ) }