import { useAtomValue } from "@effect/atom-react";import { DiffsHighlighter, getSharedHighlighter, SupportedLanguages } from "@pierre/diffs";import { CheckIcon, ChevronRightIcon, CopyIcon, GlobeIcon, Maximize2Icon, Minimize2Icon, WrapTextIcon,} from "lucide-react";import type { ScopedThreadRef, ServerProviderSkill } from "@t3tools/contracts";import { isAtomCommandInterrupted, squashAtomCommandFailure, type AtomCommandResult,} from "@t3tools/client-runtime/state/runtime";import * as Cause from "effect/Cause";import { AsyncResult } from "effect/unstable/reactivity";import React, { Children, Suspense, type ClipboardEvent as ReactClipboardEvent, type MouseEvent as ReactMouseEvent, isValidElement, use, useCallback, memo, useEffect, useMemo, useRef, useState, type ReactNode,} from "react";import type { Components } from "react-markdown";import ReactMarkdown from "react-markdown";import { defaultUrlTransform } from "react-markdown";import rehypeRaw from "rehype-raw";import rehypeSanitize, { defaultSchema } from "rehype-sanitize";import remarkBreaks from "remark-breaks";import remarkGfm from "remark-gfm";import { renderSkillInlineMarkdownChildren } from "./chat/SkillInlineText";import { CHAT_FILE_TAG_CHIP_CLASS_NAME, FileTagChipContent } from "./chat/FileTagChip";import { PierreEntryIcon } from "./chat/PierreEntryIcon";import { hasSpecificPierreIconForFileName, syntheticFileNameForLanguageId } from "../pierre-icons";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";import { Button } from "./ui/button";import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./ui/collapsible";import { ScrollArea } from "./ui/scroll-area";import { Menu, MenuItem, MenuPopup, MenuTrigger } from "./ui/menu";import { stackedThreadToast, toastManager } from "./ui/toast";import { useOpenInPreferredEditor } from "../editorPreferences";import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering";import { fnv1a32 } from "../lib/diffRendering";import { LRUCache } from "../lib/lruCache";import { useTheme } from "../hooks/useTheme";import { chatMarkdownClipboardPayload, serializeTableElementToCsv, serializeTableElementToMarkdown,} from "../markdown-clipboard";import { normalizeMarkdownLinkDestination, resolveMarkdownFileLinkMeta, rewriteMarkdownFileUriHref,} from "../markdown-links";import { readLocalApi } from "../localApi";import { cn } from "../lib/utils";import { useRightPanelStore } from "../rightPanelStore";import { useActiveEnvironmentId } from "../state/entities";import { serverEnvironment } from "../state/server";import { assetEnvironment } from "../state/assets";import { usePreparedConnection } from "../state/session";import { previewEnvironment } from "../state/preview";import { useAtomCommand } from "../state/use-atom-command";import { useAtomQueryRunner } from "../state/use-atom-query-runner";import { isPreviewSupportedInRuntime } from "../previewStateStore";import { isWorkspacePreviewFile, openFileInPreview, openUrlInPreview, BrowserPreviewEnvironmentDisconnectedError, BrowserPreviewThreadContextUnavailableError,} from "../browser/openFileInPreview"; class CodeHighlightErrorBoundary extends React.Component< { fallback: ReactNode; children: ReactNode }, { hasError: boolean }> { constructor(props: { fallback: ReactNode; children: ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } override render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; }} interface ChatMarkdownProps { text: string; cwd: string | undefined; threadRef?: ScopedThreadRef | undefined; onTaskListChange?: ((input: { markerOffset: number; checked: boolean }) => void) | undefined; isStreaming?: boolean; skills?: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">>; className?: string; /** Treat single newlines as hard breaks — chat-style user input. */ lineBreaks?: boolean;} const EMPTY_MARKDOWN_SKILLS: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">> = []; const CODE_FENCE_LANGUAGE_REGEX = /(?:^|\s)language-([^\s]+)/;const MAX_HIGHLIGHT_CACHE_ENTRIES = 500;const MAX_HIGHLIGHT_CACHE_MEMORY_BYTES = 50 * 1024 * 1024; interface MarkdownActionFailureContext { readonly operation: string; readonly target?: string; readonly format?: "markdown" | "csv"; readonly language?: string; readonly fenceTitle?: string; readonly copyTarget?: string;} function reportMarkdownActionFailure(context: MarkdownActionFailureContext, cause: unknown): void { console.error("[chat-markdown] action failed", context, cause);} const highlightedCodeCache = new LRUCache<string>( MAX_HIGHLIGHT_CACHE_ENTRIES, MAX_HIGHLIGHT_CACHE_MEMORY_BYTES,);const highlighterPromiseCache = new Map<string, Promise<DiffsHighlighter>>(); function findTaskListMarkerOffset(markdown: string, listItemStart: number): number | null { const firstLineEnd = markdown.indexOf("\n", listItemStart); const firstLine = markdown.slice( listItemStart, firstLineEnd === -1 ? markdown.length : firstLineEnd, ); const match = firstLine.match(/^(?:\s*(?:[-+*]|\d+[.)])\s+)(\[[ xX]\])/); if (!match?.[1]) return null; return listItemStart + firstLine.indexOf(match[1]);}const CHAT_MARKDOWN_SANITIZE_SCHEMA = { ...defaultSchema, attributes: { ...defaultSchema.attributes, "*": (defaultSchema.attributes?.["*"] ?? []).filter((attribute) => attribute !== "title"), code: [...(defaultSchema.attributes?.code ?? []), "dataCodeMeta"], }, protocols: { ...defaultSchema.protocols, href: [...(defaultSchema.protocols?.href ?? []), "file"], },} satisfies Parameters<typeof rehypeSanitize>[0]; function extractFenceLanguage(className: string | undefined): string { const match = className?.match(CODE_FENCE_LANGUAGE_REGEX); const raw = match?.[1] ?? "text"; // Shiki doesn't bundle a gitignore grammar; ini is a close match (#685) return raw === "gitignore" ? "ini" : raw;} const FENCE_TITLE_ATTR_REGEX = /(?:^|\s)(?:title|file(?:name)?)=(?:"([^"]+)"|'([^']+)'|(\S+))/i;const FENCE_FILENAME_TOKEN_REGEX = /^[\w@][\w@./-]*\.[A-Za-z0-9]+$/; /** Pulls a filename out of fence meta: ```ts title="x.ts" / ```ts src/main.ts */function extractFenceTitle(meta: string | undefined): string | null { if (!meta) return null; const attrMatch = FENCE_TITLE_ATTR_REGEX.exec(meta); const attrTitle = attrMatch?.[1] ?? attrMatch?.[2] ?? attrMatch?.[3]; if (attrTitle) return attrTitle; return meta.split(/\s+/).find((candidate) => FENCE_FILENAME_TOKEN_REGEX.test(candidate)) ?? null;} function extractPreCodeMeta(node: unknown): string | undefined { const children = ( node as | { children?: Array<{ type?: string; tagName?: string; data?: { meta?: unknown }; properties?: { dataCodeMeta?: unknown }; }>; } | undefined )?.children; const codeNode = children?.find((child) => child?.type === "element" && child.tagName === "code"); const meta = codeNode?.properties?.dataCodeMeta ?? codeNode?.data?.meta; return typeof meta === "string" && meta.trim().length > 0 ? meta.trim() : undefined;} type MarkdownAstNode = { type?: string; meta?: unknown; data?: { hProperties?: Record<string, unknown>; }; children?: MarkdownAstNode[];}; function remarkPreserveCodeMeta() { return (tree: MarkdownAstNode) => { const visit = (node: MarkdownAstNode) => { if (node.type === "code" && typeof node.meta === "string" && node.meta.trim().length > 0) { node.data = { ...node.data, hProperties: { ...node.data?.hProperties, dataCodeMeta: node.meta.trim(), }, }; } node.children?.forEach(visit); }; visit(tree); };} function nodeToPlainText(node: ReactNode): string { if (typeof node === "string" || typeof node === "number") { return String(node); } if (Array.isArray(node)) { return node.map((child) => nodeToPlainText(child)).join(""); } if (isValidElement<{ children?: ReactNode }>(node)) { return nodeToPlainText(node.props.children); } return "";} function extractCodeBlock( children: ReactNode,): { className: string | undefined; code: string } | null { const childNodes = Children.toArray(children); if (childNodes.length !== 1) { return null; } const onlyChild = childNodes[0]; if ( !isValidElement<{ className?: string; children?: ReactNode }>(onlyChild) || onlyChild.type !== "code" ) { return null; } return { className: onlyChild.props.className, code: nodeToPlainText(onlyChild.props.children), };} function createHighlightCacheKey(code: string, language: string, themeName: DiffThemeName): string { return `${fnv1a32(code).toString(36)}:${code.length}:${language}:${themeName}`;} function estimateHighlightedSize(html: string, code: string): number { return Math.max(html.length * 2, code.length * 3);} function getHighlighterPromise(language: string): Promise<DiffsHighlighter> { const cached = highlighterPromiseCache.get(language); if (cached) return cached; const promise = getSharedHighlighter({ themes: [resolveDiffThemeName("dark"), resolveDiffThemeName("light")], langs: [language as SupportedLanguages], preferredHighlighter: "shiki-js", }).catch((err) => { highlighterPromiseCache.delete(language); if (language === "text") { // "text" itself failed — Shiki cannot initialize at all, surface the error throw err; } // Language not supported by Shiki — fall back to "text" return getHighlighterPromise("text"); }); highlighterPromiseCache.set(language, promise); return promise;} function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) { const containerRef = useRef<HTMLDivElement | null>(null); const tableRef = useRef<HTMLTableElement | null>(null); const [expanded, setExpanded] = useState(false); const [copied, setCopied] = useState(false); const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const expandLabel = expanded ? "Collapse table cells" : "Expand table cells"; const copyLabel = copied ? "Copied" : "Copy table"; function toggleExpanded() { const table = tableRef.current; if (!table) return; if (!expanded) { const rows = [...table.rows]; const columnWidths = rows.reduce<number[]>((widths, row) => { [...row.cells].forEach((cell, columnIndex) => { widths[columnIndex] = Math.max( widths[columnIndex] ?? 0, cell.getBoundingClientRect().width, ); }); return widths; }, []); [...(table.tHead?.rows[0]?.cells ?? [])].forEach((cell, columnIndex) => { cell.style.minWidth = `${columnWidths[columnIndex] ?? cell.getBoundingClientRect().width}px`; }); } setExpanded((value) => !value); } const handleCopy = useCallback((format: "markdown" | "csv") => { const table = containerRef.current?.querySelector("table"); if (!table || typeof navigator === "undefined" || navigator.clipboard == null) { return; } const text = format === "markdown" ? serializeTableElementToMarkdown(table) : serializeTableElementToCsv(table); void navigator.clipboard .writeText(text) .then(() => { if (copiedTimerRef.current != null) { clearTimeout(copiedTimerRef.current); } setCopied(true); copiedTimerRef.current = setTimeout(() => { setCopied(false); copiedTimerRef.current = null; }, 1200); }) .catch((cause) => { reportMarkdownActionFailure({ operation: "copy-table", format }, cause); }); }, []); useEffect( () => () => { if (copiedTimerRef.current != null) { clearTimeout(copiedTimerRef.current); copiedTimerRef.current = null; } }, [], ); return ( <div ref={containerRef} className="chat-markdown-table-container" data-expanded={expanded ? "true" : "false"} > <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none" > <table ref={tableRef} {...props}> {children} </table> </ScrollArea> <div className="chat-markdown-table-footer select-none"> <Tooltip> <TooltipTrigger render={ <Button type="button" variant="ghost" size="icon-xs" className="chat-markdown-chrome-action" aria-pressed={expanded} onClick={toggleExpanded} aria-label={expandLabel} /> } > {expanded ? <Minimize2Icon className="size-3" /> : <Maximize2Icon className="size-3" />} </TooltipTrigger> <TooltipPopup side="top">{expandLabel}</TooltipPopup> </Tooltip> <Menu> <Tooltip> <TooltipTrigger render={ <MenuTrigger render={ <Button type="button" variant="ghost" size="icon-xs" className="chat-markdown-chrome-action" aria-label={copyLabel} /> } /> } > {copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />} </TooltipTrigger> <TooltipPopup side="top">{copyLabel}</TooltipPopup> </Tooltip> <MenuPopup align="end"> <MenuItem onClick={() => handleCopy("markdown")}>Copy as Markdown</MenuItem> <MenuItem onClick={() => handleCopy("csv")}>Copy as CSV</MenuItem> </MenuPopup> </Menu> </div> </div> );} function MarkdownDetails({ children, open = false,}: Pick<React.ComponentProps<"details">, "children" | "open">) { const [isOpen, setIsOpen] = useState(open); const childNodes = Children.toArray(children); const summaryIndex = childNodes.findIndex( (child) => isValidElement(child) && child.type === "summary", ); const summaryNode = summaryIndex >= 0 ? childNodes[summaryIndex] : null; const summary = isValidElement<{ children?: ReactNode }>(summaryNode) && summaryNode.props.children ? summaryNode.props.children : "Details"; const content = childNodes.filter((_, index) => index !== summaryIndex); return ( <Collapsible defaultOpen={open} onOpenChange={setIsOpen} className="chat-markdown-details my-2 border-y border-border/60" data-markdown-details="" data-markdown-details-open={isOpen ? "true" : "false"} > <CollapsibleTrigger className="flex w-full items-center gap-2 py-2 text-left text-sm font-medium text-foreground data-panel-open:[&_svg]:rotate-90" data-markdown-details-summary="" > <ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform" aria-hidden /> <span>{summary}</span> </CollapsibleTrigger> <CollapsiblePanel> <div className="pb-3 ps-6 text-foreground/80" data-markdown-details-content=""> {content} </div> </CollapsiblePanel> </Collapsible> );} /** * Filename titles render icon + text; language-only titles render just the * icon (redundant next to its own name) and fall back to the language text * when no specific icon exists or it fails to load. */function MarkdownCodeBlockTitleContent({ fenceTitle, language, theme,}: { fenceTitle: string | null; language: string; theme: "light" | "dark";}) { if (fenceTitle) { return ( <> <PierreEntryIcon pathValue={fenceTitle} kind="file" theme={theme} className="size-3.5" /> <span className="truncate">{fenceTitle}</span> </> ); } const fileName = syntheticFileNameForLanguageId(language); if (!hasSpecificPierreIconForFileName(fileName)) { return <span className="truncate">{language}</span>; }Lines 1–500 of 1570. Load more
import { useAtomValue } from "@effect/atom-react"; import { DiffsHighlighter, getSharedHighlighter, SupportedLanguages } from "@pierre/diffs"; import { CheckIcon, ChevronRightIcon, CopyIcon, GlobeIcon, Maximize2Icon, Minimize2Icon, WrapTextIcon, } from "lucide-react"; import type { ScopedThreadRef, ServerProviderSkill } from "@t3tools/contracts"; import { isAtomCommandInterrupted, squashAtomCommandFailure, type AtomCommandResult, } from "@t3tools/client-runtime/state/runtime"; import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/unstable/reactivity"; import React, { Children, Suspense, type ClipboardEvent as ReactClipboardEvent, type MouseEvent as ReactMouseEvent, isValidElement, use, useCallback, memo, useEffect, useMemo, useRef, useState, type ReactNode, } from "react"; import type { Components } from "react-markdown"; import ReactMarkdown from "react-markdown"; import { defaultUrlTransform } from "react-markdown"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import remarkBreaks from "remark-breaks"; import remarkGfm from "remark-gfm"; import { renderSkillInlineMarkdownChildren } from "./chat/SkillInlineText"; import { CHAT_FILE_TAG_CHIP_CLASS_NAME, FileTagChipContent } from "./chat/FileTagChip"; import { PierreEntryIcon } from "./chat/PierreEntryIcon"; import { hasSpecificPierreIconForFileName, syntheticFileNameForLanguageId } from "../pierre-icons"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { Button } from "./ui/button"; import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./ui/collapsible"; import { ScrollArea } from "./ui/scroll-area"; import { Menu, MenuItem, MenuPopup, MenuTrigger } from "./ui/menu"; import { stackedThreadToast, toastManager } from "./ui/toast"; import { useOpenInPreferredEditor } from "../editorPreferences"; import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering"; import { fnv1a32 } from "../lib/diffRendering"; import { LRUCache } from "../lib/lruCache"; import { useTheme } from "../hooks/useTheme"; import { chatMarkdownClipboardPayload, serializeTableElementToCsv, serializeTableElementToMarkdown, } from "../markdown-clipboard"; import { normalizeMarkdownLinkDestination, resolveMarkdownFileLinkMeta, rewriteMarkdownFileUriHref, } from "../markdown-links"; import { readLocalApi } from "../localApi"; import { cn } from "../lib/utils"; import { useRightPanelStore } from "../rightPanelStore"; import { useActiveEnvironmentId } from "../state/entities"; import { serverEnvironment } from "../state/server"; import { assetEnvironment } from "../state/assets"; import { usePreparedConnection } from "../state/session"; import { previewEnvironment } from "../state/preview"; import { useAtomCommand } from "../state/use-atom-command"; import { useAtomQueryRunner } from "../state/use-atom-query-runner"; import { isPreviewSupportedInRuntime } from "../previewStateStore"; import { isWorkspacePreviewFile, openFileInPreview, openUrlInPreview, BrowserPreviewEnvironmentDisconnectedError, BrowserPreviewThreadContextUnavailableError, } from "../browser/openFileInPreview"; class CodeHighlightErrorBoundary extends React.Component< { fallback: ReactNode; children: ReactNode }, { hasError: boolean } > { constructor(props: { fallback: ReactNode; children: ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } override render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } interface ChatMarkdownProps { text: string; cwd: string | undefined; threadRef?: ScopedThreadRef | undefined; onTaskListChange?: ((input: { markerOffset: number; checked: boolean }) => void) | undefined; isStreaming?: boolean; skills?: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">>; className?: string; /** Treat single newlines as hard breaks — chat-style user input. */ lineBreaks?: boolean; } const EMPTY_MARKDOWN_SKILLS: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">> = []; const CODE_FENCE_LANGUAGE_REGEX = /(?:^|\s)language-([^\s]+)/; const MAX_HIGHLIGHT_CACHE_ENTRIES = 500; const MAX_HIGHLIGHT_CACHE_MEMORY_BYTES = 50 * 1024 * 1024; interface MarkdownActionFailureContext { readonly operation: string; readonly target?: string; readonly format?: "markdown" | "csv"; readonly language?: string; readonly fenceTitle?: string; readonly copyTarget?: string; } function reportMarkdownActionFailure(context: MarkdownActionFailureContext, cause: unknown): void { console.error("[chat-markdown] action failed", context, cause); } const highlightedCodeCache = new LRUCache<string>( MAX_HIGHLIGHT_CACHE_ENTRIES, MAX_HIGHLIGHT_CACHE_MEMORY_BYTES, ); const highlighterPromiseCache = new Map<string, Promise<DiffsHighlighter>>(); function findTaskListMarkerOffset(markdown: string, listItemStart: number): number | null { const firstLineEnd = markdown.indexOf("\n", listItemStart); const firstLine = markdown.slice( listItemStart, firstLineEnd === -1 ? markdown.length : firstLineEnd, ); const match = firstLine.match(/^(?:\s*(?:[-+*]|\d+[.)])\s+)(\[[ xX]\])/); if (!match?.[1]) return null; return listItemStart + firstLine.indexOf(match[1]); } const CHAT_MARKDOWN_SANITIZE_SCHEMA = { ...defaultSchema, attributes: { ...defaultSchema.attributes, "*": (defaultSchema.attributes?.["*"] ?? []).filter((attribute) => attribute !== "title"), code: [...(defaultSchema.attributes?.code ?? []), "dataCodeMeta"], }, protocols: { ...defaultSchema.protocols, href: [...(defaultSchema.protocols?.href ?? []), "file"], }, } satisfies Parameters<typeof rehypeSanitize>[0]; function extractFenceLanguage(className: string | undefined): string { const match = className?.match(CODE_FENCE_LANGUAGE_REGEX); const raw = match?.[1] ?? "text"; // Shiki doesn't bundle a gitignore grammar; ini is a close match (#685) return raw === "gitignore" ? "ini" : raw; } const FENCE_TITLE_ATTR_REGEX = /(?:^|\s)(?:title|file(?:name)?)=(?:"([^"]+)"|'([^']+)'|(\S+))/i; const FENCE_FILENAME_TOKEN_REGEX = /^[\w@][\w@./-]*\.[A-Za-z0-9]+$/; /** Pulls a filename out of fence meta: ```ts title="x.ts" / ```ts src/main.ts */ function extractFenceTitle(meta: string | undefined): string | null { if (!meta) return null; const attrMatch = FENCE_TITLE_ATTR_REGEX.exec(meta); const attrTitle = attrMatch?.[1] ?? attrMatch?.[2] ?? attrMatch?.[3]; if (attrTitle) return attrTitle; return meta.split(/\s+/).find((candidate) => FENCE_FILENAME_TOKEN_REGEX.test(candidate)) ?? null; } function extractPreCodeMeta(node: unknown): string | undefined { const children = ( node as | { children?: Array<{ type?: string; tagName?: string; data?: { meta?: unknown }; properties?: { dataCodeMeta?: unknown }; }>; } | undefined )?.children; const codeNode = children?.find((child) => child?.type === "element" && child.tagName === "code"); const meta = codeNode?.properties?.dataCodeMeta ?? codeNode?.data?.meta; return typeof meta === "string" && meta.trim().length > 0 ? meta.trim() : undefined; } type MarkdownAstNode = { type?: string; meta?: unknown; data?: { hProperties?: Record<string, unknown>; }; children?: MarkdownAstNode[]; }; function remarkPreserveCodeMeta() { return (tree: MarkdownAstNode) => { const visit = (node: MarkdownAstNode) => { if (node.type === "code" && typeof node.meta === "string" && node.meta.trim().length > 0) { node.data = { ...node.data, hProperties: { ...node.data?.hProperties, dataCodeMeta: node.meta.trim(), }, }; } node.children?.forEach(visit); }; visit(tree); }; } function nodeToPlainText(node: ReactNode): string { if (typeof node === "string" || typeof node === "number") { return String(node); } if (Array.isArray(node)) { return node.map((child) => nodeToPlainText(child)).join(""); } if (isValidElement<{ children?: ReactNode }>(node)) { return nodeToPlainText(node.props.children); } return ""; } function extractCodeBlock( children: ReactNode, ): { className: string | undefined; code: string } | null { const childNodes = Children.toArray(children); if (childNodes.length !== 1) { return null; } const onlyChild = childNodes[0]; if ( !isValidElement<{ className?: string; children?: ReactNode }>(onlyChild) || onlyChild.type !== "code" ) { return null; } return { className: onlyChild.props.className, code: nodeToPlainText(onlyChild.props.children), }; } function createHighlightCacheKey(code: string, language: string, themeName: DiffThemeName): string { return `${fnv1a32(code).toString(36)}:${code.length}:${language}:${themeName}`; } function estimateHighlightedSize(html: string, code: string): number { return Math.max(html.length * 2, code.length * 3); } function getHighlighterPromise(language: string): Promise<DiffsHighlighter> { const cached = highlighterPromiseCache.get(language); if (cached) return cached; const promise = getSharedHighlighter({ themes: [resolveDiffThemeName("dark"), resolveDiffThemeName("light")], langs: [language as SupportedLanguages], preferredHighlighter: "shiki-js", }).catch((err) => { highlighterPromiseCache.delete(language); if (language === "text") { // "text" itself failed — Shiki cannot initialize at all, surface the error throw err; } // Language not supported by Shiki — fall back to "text" return getHighlighterPromise("text"); }); highlighterPromiseCache.set(language, promise); return promise; } function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) { const containerRef = useRef<HTMLDivElement | null>(null); const tableRef = useRef<HTMLTableElement | null>(null); const [expanded, setExpanded] = useState(false); const [copied, setCopied] = useState(false); const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const expandLabel = expanded ? "Collapse table cells" : "Expand table cells"; const copyLabel = copied ? "Copied" : "Copy table"; function toggleExpanded() { const table = tableRef.current; if (!table) return; if (!expanded) { const rows = [...table.rows]; const columnWidths = rows.reduce<number[]>((widths, row) => { [...row.cells].forEach((cell, columnIndex) => { widths[columnIndex] = Math.max( widths[columnIndex] ?? 0, cell.getBoundingClientRect().width, ); }); return widths; }, []); [...(table.tHead?.rows[0]?.cells ?? [])].forEach((cell, columnIndex) => { cell.style.minWidth = `${columnWidths[columnIndex] ?? cell.getBoundingClientRect().width}px`; }); } setExpanded((value) => !value); } const handleCopy = useCallback((format: "markdown" | "csv") => { const table = containerRef.current?.querySelector("table"); if (!table || typeof navigator === "undefined" || navigator.clipboard == null) { return; } const text = format === "markdown" ? serializeTableElementToMarkdown(table) : serializeTableElementToCsv(table); void navigator.clipboard .writeText(text) .then(() => { if (copiedTimerRef.current != null) { clearTimeout(copiedTimerRef.current); } setCopied(true); copiedTimerRef.current = setTimeout(() => { setCopied(false); copiedTimerRef.current = null; }, 1200); }) .catch((cause) => { reportMarkdownActionFailure({ operation: "copy-table", format }, cause); }); }, []); useEffect( () => () => { if (copiedTimerRef.current != null) { clearTimeout(copiedTimerRef.current); copiedTimerRef.current = null; } }, [], ); return ( <div ref={containerRef} className="chat-markdown-table-container" data-expanded={expanded ? "true" : "false"} > <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none" > <table ref={tableRef} {...props}> {children} </table> </ScrollArea> <div className="chat-markdown-table-footer select-none"> <Tooltip> <TooltipTrigger render={ <Button type="button" variant="ghost" size="icon-xs" className="chat-markdown-chrome-action" aria-pressed={expanded} onClick={toggleExpanded} aria-label={expandLabel} /> } > {expanded ? <Minimize2Icon className="size-3" /> : <Maximize2Icon className="size-3" />} </TooltipTrigger> <TooltipPopup side="top">{expandLabel}</TooltipPopup> </Tooltip> <Menu> <Tooltip> <TooltipTrigger render={ <MenuTrigger render={ <Button type="button" variant="ghost" size="icon-xs" className="chat-markdown-chrome-action" aria-label={copyLabel} /> } /> } > {copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />} </TooltipTrigger> <TooltipPopup side="top">{copyLabel}</TooltipPopup> </Tooltip> <MenuPopup align="end"> <MenuItem onClick={() => handleCopy("markdown")}>Copy as Markdown</MenuItem> <MenuItem onClick={() => handleCopy("csv")}>Copy as CSV</MenuItem> </MenuPopup> </Menu> </div> </div> ); } function MarkdownDetails({ children, open = false, }: Pick<React.ComponentProps<"details">, "children" | "open">) { const [isOpen, setIsOpen] = useState(open); const childNodes = Children.toArray(children); const summaryIndex = childNodes.findIndex( (child) => isValidElement(child) && child.type === "summary", ); const summaryNode = summaryIndex >= 0 ? childNodes[summaryIndex] : null; const summary = isValidElement<{ children?: ReactNode }>(summaryNode) && summaryNode.props.children ? summaryNode.props.children : "Details"; const content = childNodes.filter((_, index) => index !== summaryIndex); return ( <Collapsible defaultOpen={open} onOpenChange={setIsOpen} className="chat-markdown-details my-2 border-y border-border/60" data-markdown-details="" data-markdown-details-open={isOpen ? "true" : "false"} > <CollapsibleTrigger className="flex w-full items-center gap-2 py-2 text-left text-sm font-medium text-foreground data-panel-open:[&_svg]:rotate-90" data-markdown-details-summary="" > <ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform" aria-hidden /> <span>{summary}</span> </CollapsibleTrigger> <CollapsiblePanel> <div className="pb-3 ps-6 text-foreground/80" data-markdown-details-content=""> {content} </div> </CollapsiblePanel> </Collapsible> ); } /** * Filename titles render icon + text; language-only titles render just the * icon (redundant next to its own name) and fall back to the language text * when no specific icon exists or it fails to load. */ function MarkdownCodeBlockTitleContent({ fenceTitle, language, theme, }: { fenceTitle: string | null; language: string; theme: "light" | "dark"; }) { if (fenceTitle) { return ( <> <PierreEntryIcon pathValue={fenceTitle} kind="file" theme={theme} className="size-3.5" /> <span className="truncate">{fenceTitle}</span> </> ); } const fileName = syntheticFileNameForLanguageId(language); if (!hasSpecificPierreIconForFileName(fileName)) { return <span className="truncate">{language}</span>; }