import * as React from "react";
import { settingsApi } from "../api/settings";
// Matches patterns like `src/main.rs:42` or `path/to/file.tsx:123`
// Path must contain at least one dot (file extension) and a colon followed by digits.
const CODE_REF_PATTERN = /\b([\w.\-/]+\.\w+):(\d+)\b/g;
export interface CodeRefPart {
type: "text" | "ref";
value: string;
path?: string;
line?: number;
}
/**
* Parse a string into text and code-reference parts.
* Code references have the format `path/to/file.ext:line`.
*/
export function parseCodeRefs(text: string): CodeRefPart[] {
const parts: CodeRefPart[] = [];
let lastIndex = 0;
const re = new RegExp(CODE_REF_PATTERN.source, "g");
let match: RegExpExecArray | null;
match = re.exec(text);
while (match !== null) {
if (match.index > lastIndex) {
parts.push({ type: "text", value: text.slice(lastIndex, match.index) });
}
parts.push({
type: "ref",
value: match[0],
path: match[1],
line: Number(match[2]),
});
lastIndex = re.lastIndex;
match = re.exec(text);
}
if (lastIndex < text.length) {
parts.push({ type: "text", value: text.slice(lastIndex) });
}
return parts;
}
interface CodeRefLinkProps {
path: string;
line: number;
children: React.ReactNode;
}
function CodeRefLink({ path, line, children }: CodeRefLinkProps) {
const handleClick = React.useCallback(() => {
settingsApi.openFile(path, line).catch(() => {
// Silently ignore errors (e.g. no editor configured)
});
}, [path, line]);
return (
);
}
interface InlineCodeWithRefsProps {
text: string;
}
/**
* Renders inline text with code references converted to clickable links.
*/
export function InlineCodeWithRefs({ text }: InlineCodeWithRefsProps) {
const parts = parseCodeRefs(text);
if (parts.length === 1 && parts[0].type === "text") {
return <>{text}>;
}
return (
<>
{parts.map((part) => {
if (
part.type === "ref" &&
part.path !== undefined &&
part.line !== undefined
) {
return (
{part.value}
);
}
return {part.value};
})}
>
);
}