import { Suspense, createEffect, createMemo, createSignal, lazy, onMount, type Accessor, type JSXElement } from "solid-js" import type { ToolState } from "@opencode-ai/sdk/v2" import useMediaQuery from "@suid/material/useMediaQuery" import { AlignJustify, Copy, Split, WrapText } from "lucide-solid" import type { RenderCache } from "../../types/message" import type { DiffViewMode } from "../../stores/preferences" import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types" import { getRelativePath } from "./utils" import { getCacheEntry } from "../../lib/global-cache" import { copyToClipboard } from "../../lib/clipboard" const LazyToolCallDiffViewer = lazy(() => import("../diff-viewer").then((module) => ({ default: module.ToolCallDiffViewer })), ) function CachedDiffMarkup(props: { html: string; onRendered?: () => void }) { onMount(() => { props.onRendered?.() }) return (
) } type CacheHandle = { get(): T | undefined params(): unknown } type DiffPrefs = { diffViewMode?: DiffViewMode } export function createDiffContentRenderer(params: { toolState: Accessor preferences: Accessor setDiffViewMode: (mode: DiffViewMode) => void isDark: Accessor t: (key: string, params?: Record) => string diffCache: CacheHandle permissionDiffCache: CacheHandle scrollHelpers: ToolScrollHelpers handleScrollRendered: () => void onContentRendered?: () => void }) { const compactDiffQuery = useMediaQuery("(max-width: 640px)") const [mobileModeOverride, setMobileModeOverride] = createSignal(undefined) const [wordWrapEnabled, setWordWrapEnabled] = createSignal(true) createEffect(() => { if (!compactDiffQuery()) { setMobileModeOverride(undefined) } }) const registerTracked = (element: HTMLDivElement | null) => { params.scrollHelpers.registerContainer(element) } const registerUntracked = (element: HTMLDivElement | null) => { params.scrollHelpers.registerContainer(element, { disableTracking: true }) } function renderDiffContent(payload: DiffPayload, options?: DiffRenderOptions): JSXElement | null { const relativePath = payload.filePath ? getRelativePath(payload.filePath) : "" const toolbarLabel = options?.label || (relativePath ? params.t("toolCall.diff.label.withPath", { path: relativePath }) : params.t("toolCall.diff.label")) const selectedVariant = options?.variant === "permission-diff" ? "permission-diff" : "diff" const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache const preferredMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode const effectiveMode = () => { if (!compactDiffQuery()) return preferredMode() return mobileModeOverride() || "unified" } const shouldWrap = () => wordWrapEnabled() const themeKey = params.isDark() ? "dark" : "light" const state = params.toolState() const disableScrollTracking = Boolean( options?.disableScrollTracking || (state?.status !== "running" && state?.status !== "pending"), ) const registerRef = disableScrollTracking ? registerUntracked : registerTracked const baseEntryParams = cacheHandle.params() as any const cacheEntryParams = (() => { const suffix = typeof options?.cacheKey === "string" ? options.cacheKey.trim() : "" if (!suffix) return baseEntryParams return { ...baseEntryParams, cacheId: `${baseEntryParams.cacheId}:${suffix}`, } })() const currentMode = createMemo(() => effectiveMode()) const currentWrap = createMemo(() => shouldWrap()) const cachedHtml = createMemo(() => { const cached = getCacheEntry(cacheEntryParams) if ( cached && cached.text === payload.diffText && cached.theme === themeKey && cached.mode === currentMode() && cached.wrap === currentWrap() ) { return cached.html } return undefined }) const handleModeChange = (mode: DiffViewMode) => { if (compactDiffQuery()) { setMobileModeOverride(mode) } params.setDiffViewMode(mode) } const nextViewMode = (): DiffViewMode => (currentMode() === "split" ? "unified" : "split") const viewModeTitle = () => nextViewMode() === "split" ? params.t("toolCall.diff.switchToSplit") : params.t("toolCall.diff.switchToUnified") const wordWrapTitle = () => wordWrapEnabled() ? params.t("toolCall.diff.disableWordWrap") : params.t("toolCall.diff.enableWordWrap") const copyPatchTitle = () => params.t("toolCall.diff.copyPatch") const handleDiffRendered = () => { params.handleScrollRendered() params.onContentRendered?.() } return (
{toolbarLabel}
{cachedHtml() ? ( ) : ( {payload.diffText}}> )} {params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
) } return { renderDiffContent } }