import { useMemo } from "react"; import type { NormalizedRegexNode } from "../regex/model/syntax"; import { handleTreeNavigation } from "./tree-navigation"; const MAXIMUM_RENDERED_EXPLANATION_NODES = 2_000; export interface ExplanationTreeProps { readonly root?: NormalizedRegexNode; readonly selectedId?: string; readonly flags?: readonly string[]; readonly stale?: boolean; readonly onSelect: (node: NormalizedRegexNode) => void; } function countNodes(root: NormalizedRegexNode): number { let count = 0; const pending = [root]; while (pending.length > 0) { const node = pending.pop(); if (!node) continue; count += 1; for (const child of node.children) pending.push(child); } return count; } function boundedRoot( root: NormalizedRegexNode, maximum: number, ): NormalizedRegexNode { let remaining = maximum; const takeNode = ( node: NormalizedRegexNode, ): NormalizedRegexNode | undefined => { if (remaining <= 0) return undefined; remaining -= 1; const children: NormalizedRegexNode[] = []; for (const child of node.children) { const visible = takeNode(child); if (!visible) break; children.push(visible); } return { ...node, children }; }; return takeNode(root) ?? root; } function containsNode(root: NormalizedRegexNode, id: string): boolean { const pending = [root]; while (pending.length > 0) { const node = pending.pop(); if (!node) continue; if (node.id === id) return true; for (const child of node.children) pending.push(child); } return false; } function lengthLabel(node: NormalizedRegexNode): string { const minimum = node.properties.minimumLength; const maximum = node.properties.maximumLength; if (minimum === undefined && maximum === undefined) return "length unknown"; return `${minimum ?? "?"}…${maximum === null ? "∞" : (maximum ?? "?")}`; } function consumesInputLabel( value: NormalizedRegexNode["properties"]["consumesInput"], ): string { return value === true ? "yes" : value === false ? "no" : value; } function nullableLabel( value: NormalizedRegexNode["properties"]["nullable"], ): string { return value === true ? "yes" : value === false ? "no" : value; } function ExplanationNode({ node, selectedId, onSelect, depth, focusableId, flags, }: { readonly node: NormalizedRegexNode; readonly selectedId?: string; readonly onSelect: (node: NormalizedRegexNode) => void; readonly depth: number; readonly focusableId: string; readonly flags: readonly string[]; }) { const compatibility = node.support.notes.length > 0 ? node.support.notes.slice(0, 2).join("; ").slice(0, 240) : "no parser warning"; return (
Pattern structure
Tree preview limited. Showing the first{" "} {renderedNodes.toLocaleString()} of {totalNodes.toLocaleString()}{" "} normalized syntax nodes. The complete normalized syntax tree remains available to the application. {!selectedNodeVisible ? " The selected pattern node is outside this bounded tree preview." : ""}
) : null} {visibleRoot ? (The syntax worker is preparing the tree.
)}