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)),
);
}