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