import "./filePathLinkify.css"; import React, { cloneElement, isValidElement } from "react"; import type { ReactElement, ReactNode } from "react"; import { useFileBrowser } from "../context/FileBrowserContext"; // Two branches: the main branch requires a slash plus extension to avoid plain-prose false positives, // while the allowlist branch covers well-known root files agents commonly reference without a slash. export const FILE_PATH_REGEX = /(? prefix.endsWith(protocol))) { return true; } const { path } = parseFilePathMatch(rawMatch); if (isVersionLike(path)) { return true; } if (!hasPathSeparatorOrAllowlist(path)) { return true; } return false; } export function FilePathLink({ path, line, col, children, }: { path: string; line?: number; col?: number; children?: ReactNode; }) { const fileBrowser = useFileBrowser(); if (!fileBrowser) { return {children ?? path}; } return ( ); } export function linkifyFilePaths(text: string, options?: { keyPrefix?: string }): ReactNode[] { if (!text) { return [text]; } const nodes: ReactNode[] = []; let lastIndex = 0; let matchIndex = 0; for (const match of text.matchAll(FILE_PATH_REGEX)) { const rawMatch = match[0]; const start = match.index ?? 0; const end = start + rawMatch.length; if (isExcludedMatch(text, start, rawMatch)) { continue; } if (start > lastIndex) { nodes.push(text.slice(lastIndex, start)); } const { path, line, col } = parseFilePathMatch(rawMatch); nodes.push( {rawMatch} , ); lastIndex = end; matchIndex += 1; } if (lastIndex === 0) { return [text]; } if (lastIndex < text.length) { nodes.push(text.slice(lastIndex)); } return nodes; } export function linkifyReactChildren(children: ReactNode): ReactNode { if (typeof children === "string") { const nodes = linkifyFilePaths(children); return nodes.length === 1 ? nodes[0] : <>{nodes}; } if (Array.isArray(children)) { return React.Children.map(children, (child) => linkifyReactChildren(child)); } if (!isValidElement<{ children?: ReactNode }>(children)) { return children; } if (typeof children.type === "string" && ["button", "code", "pre"].includes(children.type)) { return children; } if (children.props.children === undefined) { return children; } return cloneElement( children as ReactElement<{ children?: ReactNode }>, undefined, React.Children.map(children.props.children, (child) => linkifyReactChildren(child)), ); }