/** `ast_grep` — structural AST pattern search across files. */ import type { ReactNode } from "react"; import { Badge, Badges, CodeBlock, InvalidArg, Kv, KvGrid, Output, PathText, ResultText } from "../parts"; import type { ToolRenderer, ToolRenderProps } from "../types"; import { detailsRecord, normalizeWs, num, scopePaths, str, truncate } from "../util"; /** `pat` is a string in the current schema, an array on the legacy wire. */ function patternsOf(args: Record): string[] { if (typeof args.pat === "string") return [args.pat]; if (Array.isArray(args.pat)) return args.pat.filter((p): p is string => typeof p === "string"); return []; } function Summary({ args }: ToolRenderProps): ReactNode { const patterns = patternsOf(args); const paths = scopePaths(args); const lang = str(args.lang); return ( <> {patterns.length ? truncate(normalizeWs(patterns[0]!), 64) : "?"} {patterns.length > 1 && +{patterns.length - 1}} {paths.length > 0 && } {paths.length > 1 && +{paths.length - 1}} {lang && {lang}} ); } function Body({ args, result }: ToolRenderProps): ReactNode { const patterns = patternsOf(args); const paths = scopePaths(args); const lang = str(args.lang); const glob = str(args.glob); const sel = str(args.sel); const skip = num(args.skip); const details = detailsRecord(result); const matchCount = num(details?.matchCount); const fileCount = num(details?.fileCount); const filesSearched = num(details?.filesSearched); const limitReached = details?.limitReached === true; const scopePath = str(details?.scopePath); const parseErrors = Array.isArray(details?.parseErrors) ? details.parseErrors.filter((e): e is string => typeof e === "string") : []; const parseErrorsTotal = num(details?.parseErrorsTotal) ?? parseErrors.length; const argBadges: ReactNode[] = [ lang && ( {lang} ), glob && glob={glob}, sel && sel={sel}, skip !== null && skip > 0 && skip:{skip}, ]; const resultBadges: ReactNode[] = result && !result.isError ? [ matchCount !== null && ( {matchCount} {matchCount === 1 ? "match" : "matches"} ), fileCount !== null && fileCount > 0 && ( {fileCount} {fileCount === 1 ? "file" : "files"} ), filesSearched !== null && searched {filesSearched}, limitReached && ( limit reached ), ] : []; return ( <> {patterns.length === 0 ? ( ) : ( patterns.map((pat, i) => ( 1 ? `pattern ${i + 1}` : "pattern"} maxLines={12} /> )) )} {(paths.length > 0 || scopePath) && ( {paths.length > 0 && ( {paths.map((p, i) => ( {i > 0 && ", "} ))} )} {scopePath && ( )} )} {parseErrors.length > 0 && ( parseErrors.length ? `parse issues (${parseErrorsTotal} total)` : "parse issues" } /> )} ); } export const astGrepRenderer: ToolRenderer = { Summary, Body };