Merge branch 'dev' of github.com:NeuralNomadsAI/CodeNomad into dev
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { createMemo, Show, createEffect, onCleanup } from "solid-js"
|
import { createMemo, Show, createEffect } from "solid-js"
|
||||||
import { DiffView, DiffModeEnum } from "@git-diff-view/solid"
|
import { DiffView, DiffModeEnum } from "@git-diff-view/solid"
|
||||||
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||||
import { disableCache } from "@git-diff-view/core"
|
import { disableCache } from "@git-diff-view/core"
|
||||||
@@ -20,6 +20,7 @@ interface ToolCallDiffViewerProps {
|
|||||||
filePath?: string
|
filePath?: string
|
||||||
theme: "light" | "dark"
|
theme: "light" | "dark"
|
||||||
mode: DiffViewMode
|
mode: DiffViewMode
|
||||||
|
wrap?: boolean
|
||||||
onRendered?: () => void
|
onRendered?: () => void
|
||||||
cachedHtml?: string
|
cachedHtml?: string
|
||||||
cacheEntryParams?: CacheEntryParams
|
cacheEntryParams?: CacheEntryParams
|
||||||
@@ -31,11 +32,183 @@ type DiffData = {
|
|||||||
hunks: string[]
|
hunks: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type CaptureContext = {
|
function measureTextWidth(container: HTMLElement, text: string, source: HTMLElement) {
|
||||||
theme: ToolCallDiffViewerProps["theme"]
|
const computed = window.getComputedStyle(source)
|
||||||
mode: DiffViewMode
|
const probe = document.createElement("span")
|
||||||
diffText: string
|
probe.textContent = text || ""
|
||||||
cacheEntryParams?: CacheEntryParams
|
probe.style.position = "absolute"
|
||||||
|
probe.style.visibility = "hidden"
|
||||||
|
probe.style.pointerEvents = "none"
|
||||||
|
probe.style.display = "inline-block"
|
||||||
|
probe.style.width = "auto"
|
||||||
|
probe.style.maxWidth = "none"
|
||||||
|
probe.style.whiteSpace = "nowrap"
|
||||||
|
probe.style.fontFamily = computed.fontFamily
|
||||||
|
probe.style.fontSize = computed.fontSize
|
||||||
|
probe.style.fontWeight = computed.fontWeight
|
||||||
|
probe.style.fontStyle = computed.fontStyle
|
||||||
|
probe.style.letterSpacing = computed.letterSpacing
|
||||||
|
probe.style.fontVariant = computed.fontVariant
|
||||||
|
probe.style.textTransform = computed.textTransform
|
||||||
|
probe.style.lineHeight = computed.lineHeight
|
||||||
|
container.appendChild(probe)
|
||||||
|
const width = Math.ceil(probe.getBoundingClientRect().width)
|
||||||
|
probe.remove()
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeCompactWidth(
|
||||||
|
container: HTMLElement,
|
||||||
|
entries: Array<{ text: string; source: HTMLElement }>,
|
||||||
|
maxWidthPx = 40,
|
||||||
|
) {
|
||||||
|
const measuredLabelWidthPx = entries.reduce((max, entry) => {
|
||||||
|
return Math.max(max, measureTextWidth(container, entry.text, entry.source))
|
||||||
|
}, 0)
|
||||||
|
const fallbackTextLength = entries.reduce((max, entry) => Math.max(max, entry.text.length), 1)
|
||||||
|
const fallbackWidthPx = Math.round(fallbackTextLength * 7 + 4)
|
||||||
|
return Math.max(2, Math.min(maxWidthPx, measuredLabelWidthPx > 0 ? measuredLabelWidthPx + 2 : fallbackWidthPx))
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCompactUnifiedGutter(container: HTMLElement, wrap: boolean) {
|
||||||
|
const tableWrapper = container.querySelector<HTMLElement>(".unified-diff-table-wrapper")
|
||||||
|
const table = container.querySelector<HTMLTableElement>(".unified-diff-table")
|
||||||
|
const numberCol = container.querySelector<HTMLTableColElement>(".unified-diff-table-num-col")
|
||||||
|
const gutterRows = container.querySelectorAll<HTMLElement>(".diff-line-num")
|
||||||
|
const hunkGutters = container.querySelectorAll<HTMLElement>(".diff-line-hunk-action, .diff-line-widget-wrapper, .diff-line-extend-wrapper")
|
||||||
|
const entries: Array<{ gutter: HTMLElement; label: HTMLElement; text: string }> = []
|
||||||
|
|
||||||
|
if (table) {
|
||||||
|
if (wrap) {
|
||||||
|
table.classList.add("table-fixed")
|
||||||
|
table.style.tableLayout = "fixed"
|
||||||
|
table.style.width = "100%"
|
||||||
|
table.style.minWidth = "100%"
|
||||||
|
} else {
|
||||||
|
table.classList.remove("table-fixed")
|
||||||
|
table.style.tableLayout = "auto"
|
||||||
|
table.style.width = "max-content"
|
||||||
|
table.style.minWidth = "100%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gutterRows.forEach((gutter) => {
|
||||||
|
const oldSpan = gutter.querySelector<HTMLElement>("[data-line-old-num]")
|
||||||
|
const newSpan = gutter.querySelector<HTMLElement>("[data-line-new-num]")
|
||||||
|
const spacer = gutter.querySelector<HTMLElement>(".shrink-0")
|
||||||
|
const flexWrapper = gutter.querySelector<HTMLElement>(":scope > .flex")
|
||||||
|
const currentLabel = gutter.querySelector<HTMLElement>(":scope > .tool-call-diff-compact-line-number")
|
||||||
|
|
||||||
|
const oldText = oldSpan?.textContent?.trim() ?? ""
|
||||||
|
const newText = newSpan?.textContent?.trim() ?? ""
|
||||||
|
const hasUsableNew = newText.length > 0 && newText !== "0"
|
||||||
|
const hasUsableOld = oldText.length > 0 && oldText !== "0"
|
||||||
|
const visibleText = hasUsableNew ? newText : hasUsableOld ? oldText : newText || oldText
|
||||||
|
|
||||||
|
if (flexWrapper) flexWrapper.style.display = "none"
|
||||||
|
if (spacer) spacer.style.display = "none"
|
||||||
|
if (oldSpan) { oldSpan.style.display = "none"; oldSpan.style.width = "auto" }
|
||||||
|
if (newSpan) { newSpan.style.display = "none"; newSpan.style.width = "auto" }
|
||||||
|
|
||||||
|
gutter.style.paddingLeft = "1px"
|
||||||
|
gutter.style.paddingRight = "1px"
|
||||||
|
gutter.style.textAlign = "left"
|
||||||
|
|
||||||
|
const label = currentLabel ?? document.createElement("span")
|
||||||
|
label.className = "tool-call-diff-compact-line-number"
|
||||||
|
label.textContent = visibleText
|
||||||
|
label.setAttribute("aria-hidden", visibleText ? "false" : "true")
|
||||||
|
if (!currentLabel) gutter.appendChild(label)
|
||||||
|
|
||||||
|
entries.push({ gutter, label, text: visibleText })
|
||||||
|
})
|
||||||
|
|
||||||
|
const gutterWidthPx = computeCompactWidth(container, entries.map((entry) => ({ text: entry.text, source: entry.label })))
|
||||||
|
const gutterWidth = `${gutterWidthPx}px`
|
||||||
|
const compactAsideWidth = `${Math.max(8, gutterWidthPx - 10)}px`
|
||||||
|
|
||||||
|
if (tableWrapper) {
|
||||||
|
tableWrapper.style.setProperty("--diff-aside-width", compactAsideWidth)
|
||||||
|
tableWrapper.style.setProperty("--diff-aside-width--", compactAsideWidth)
|
||||||
|
}
|
||||||
|
if (numberCol) {
|
||||||
|
numberCol.style.width = gutterWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.forEach(({ gutter, label }) => {
|
||||||
|
gutter.style.width = gutterWidth
|
||||||
|
gutter.style.minWidth = gutterWidth
|
||||||
|
gutter.style.maxWidth = gutterWidth
|
||||||
|
label.style.width = "auto"
|
||||||
|
label.style.maxWidth = "none"
|
||||||
|
})
|
||||||
|
|
||||||
|
hunkGutters.forEach((gutter) => {
|
||||||
|
gutter.style.width = gutterWidth
|
||||||
|
gutter.style.minWidth = gutterWidth
|
||||||
|
gutter.style.maxWidth = gutterWidth
|
||||||
|
gutter.style.paddingLeft = "0"
|
||||||
|
gutter.style.paddingRight = "0"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCompactSplitGutter(container: HTMLElement) {
|
||||||
|
const oldWrapper = container.querySelector<HTMLElement>(".old-diff-table-wrapper")
|
||||||
|
const newWrapper = container.querySelector<HTMLElement>(".new-diff-table-wrapper")
|
||||||
|
const numberCells = Array.from(container.querySelectorAll<HTMLElement>(".diff-line-old-num, .diff-line-new-num"))
|
||||||
|
const hunkActions = Array.from(container.querySelectorAll<HTMLElement>(".diff-line-hunk-action, .diff-line-widget-wrapper, .diff-line-extend-wrapper"))
|
||||||
|
const numberSpans = numberCells
|
||||||
|
.map((cell) => ({ cell, span: cell.querySelector<HTMLElement>("[data-line-num]") }))
|
||||||
|
.filter((entry): entry is { cell: HTMLElement; span: HTMLElement } => Boolean(entry.span))
|
||||||
|
|
||||||
|
const gutterWidthPx = computeCompactWidth(
|
||||||
|
container,
|
||||||
|
numberSpans.map(({ span }) => ({ text: span.textContent?.trim() ?? "", source: span })),
|
||||||
|
64,
|
||||||
|
)
|
||||||
|
const gutterWidth = `${gutterWidthPx}px`
|
||||||
|
|
||||||
|
;[oldWrapper, newWrapper].forEach((wrapper) => {
|
||||||
|
if (wrapper) {
|
||||||
|
wrapper.style.setProperty("--diff-aside-width", gutterWidth)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
numberCells.forEach((cell) => {
|
||||||
|
cell.style.width = gutterWidth
|
||||||
|
cell.style.minWidth = gutterWidth
|
||||||
|
cell.style.maxWidth = gutterWidth
|
||||||
|
cell.style.paddingLeft = "2px"
|
||||||
|
cell.style.paddingRight = "2px"
|
||||||
|
cell.style.textAlign = "left"
|
||||||
|
cell.style.whiteSpace = "nowrap"
|
||||||
|
cell.style.overflowWrap = "normal"
|
||||||
|
cell.style.wordBreak = "normal"
|
||||||
|
})
|
||||||
|
|
||||||
|
numberSpans.forEach(({ span }) => {
|
||||||
|
span.style.whiteSpace = "nowrap"
|
||||||
|
span.style.overflowWrap = "normal"
|
||||||
|
span.style.wordBreak = "normal"
|
||||||
|
})
|
||||||
|
|
||||||
|
hunkActions.forEach((cell) => {
|
||||||
|
cell.style.width = gutterWidth
|
||||||
|
cell.style.minWidth = gutterWidth
|
||||||
|
cell.style.maxWidth = gutterWidth
|
||||||
|
cell.style.paddingLeft = "0"
|
||||||
|
cell.style.paddingRight = "0"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCompactDiffLayout(container: HTMLElement, mode: DiffViewMode, wrap = false) {
|
||||||
|
if (mode === "unified") {
|
||||||
|
applyCompactUnifiedGutter(container, wrap)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (mode === "split") {
|
||||||
|
applyCompactSplitGutter(container)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||||
@@ -67,12 +240,15 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
|||||||
const contextKey = createMemo(() => {
|
const contextKey = createMemo(() => {
|
||||||
const data = diffData()
|
const data = diffData()
|
||||||
if (!data) return ""
|
if (!data) return ""
|
||||||
return `${props.theme}|${props.mode}|${props.diffText}`
|
return `${props.theme}|${props.mode}|${props.wrap ? "wrap" : "nowrap"}|${props.diffText}`
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const cachedHtml = props.cachedHtml
|
const cachedHtml = props.cachedHtml
|
||||||
if (cachedHtml) {
|
if (cachedHtml) {
|
||||||
|
if (diffContainerRef) {
|
||||||
|
applyCompactDiffLayout(diffContainerRef, props.mode, Boolean(props.wrap))
|
||||||
|
}
|
||||||
// When we are given cached HTML, we rely on the caller's cache
|
// When we are given cached HTML, we rely on the caller's cache
|
||||||
// and simply notify once rendered.
|
// and simply notify once rendered.
|
||||||
props.onRendered?.()
|
props.onRendered?.()
|
||||||
@@ -86,6 +262,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
|||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!diffContainerRef) return
|
if (!diffContainerRef) return
|
||||||
|
applyCompactDiffLayout(diffContainerRef, props.mode, Boolean(props.wrap))
|
||||||
const markup = diffContainerRef.innerHTML
|
const markup = diffContainerRef.innerHTML
|
||||||
if (!markup) return
|
if (!markup) return
|
||||||
lastCapturedKey = key
|
lastCapturedKey = key
|
||||||
@@ -95,6 +272,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
|||||||
html: markup,
|
html: markup,
|
||||||
theme: props.theme,
|
theme: props.theme,
|
||||||
mode: props.mode,
|
mode: props.mode,
|
||||||
|
wrap: props.wrap,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
props.onRendered?.()
|
props.onRendered?.()
|
||||||
@@ -122,7 +300,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
|||||||
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
|
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
|
||||||
diffViewTheme={props.theme}
|
diffViewTheme={props.theme}
|
||||||
diffViewHighlight
|
diffViewHighlight
|
||||||
diffViewWrap={false}
|
diffViewWrap={Boolean(props.wrap)}
|
||||||
diffViewFontSize={13}
|
diffViewFontSize={13}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
@@ -131,7 +309,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div innerHTML={props.cachedHtml} />
|
<div ref={diffContainerRef} innerHTML={props.cachedHtml} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
import { Suspense, createEffect, createMemo, createSignal, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
||||||
import type { ToolState } from "@opencode-ai/sdk/v2"
|
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 { RenderCache } from "../../types/message"
|
||||||
import type { DiffViewMode } from "../../stores/preferences"
|
import type { DiffViewMode } from "../../stores/preferences"
|
||||||
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
||||||
import { getRelativePath } from "./utils"
|
import { getRelativePath } from "./utils"
|
||||||
import { getCacheEntry } from "../../lib/global-cache"
|
import { getCacheEntry } from "../../lib/global-cache"
|
||||||
|
import { copyToClipboard } from "../../lib/clipboard"
|
||||||
|
|
||||||
const LazyToolCallDiffViewer = lazy(() =>
|
const LazyToolCallDiffViewer = lazy(() =>
|
||||||
import("../diff-viewer").then((module) => ({ default: module.ToolCallDiffViewer })),
|
import("../diff-viewer").then((module) => ({ default: module.ToolCallDiffViewer })),
|
||||||
@@ -43,6 +46,16 @@ export function createDiffContentRenderer(params: {
|
|||||||
handleScrollRendered: () => void
|
handleScrollRendered: () => void
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const compactDiffQuery = useMediaQuery("(max-width: 640px)")
|
||||||
|
const [mobileModeOverride, setMobileModeOverride] = createSignal<DiffViewMode | undefined>(undefined)
|
||||||
|
const [wordWrapEnabled, setWordWrapEnabled] = createSignal(true)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!compactDiffQuery()) {
|
||||||
|
setMobileModeOverride(undefined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const registerTracked = (element: HTMLDivElement | null) => {
|
const registerTracked = (element: HTMLDivElement | null) => {
|
||||||
params.scrollHelpers.registerContainer(element)
|
params.scrollHelpers.registerContainer(element)
|
||||||
}
|
}
|
||||||
@@ -58,7 +71,12 @@ export function createDiffContentRenderer(params: {
|
|||||||
: params.t("toolCall.diff.label"))
|
: params.t("toolCall.diff.label"))
|
||||||
const selectedVariant = options?.variant === "permission-diff" ? "permission-diff" : "diff"
|
const selectedVariant = options?.variant === "permission-diff" ? "permission-diff" : "diff"
|
||||||
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
||||||
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
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 themeKey = params.isDark() ? "dark" : "light"
|
||||||
const state = params.toolState()
|
const state = params.toolState()
|
||||||
const disableScrollTracking = Boolean(
|
const disableScrollTracking = Boolean(
|
||||||
@@ -76,17 +94,40 @@ export function createDiffContentRenderer(params: {
|
|||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
let cachedHtml: string | undefined
|
const currentMode = createMemo(() => effectiveMode())
|
||||||
const cached = getCacheEntry<RenderCache>(cacheEntryParams)
|
const currentWrap = createMemo(() => shouldWrap())
|
||||||
const currentMode = diffMode()
|
const cachedHtml = createMemo(() => {
|
||||||
if (cached && cached.text === payload.diffText && cached.theme === themeKey && cached.mode === currentMode) {
|
const cached = getCacheEntry<RenderCache>(cacheEntryParams)
|
||||||
cachedHtml = cached.html
|
if (
|
||||||
}
|
cached
|
||||||
|
&& cached.text === payload.diffText
|
||||||
|
&& cached.theme === themeKey
|
||||||
|
&& cached.mode === currentMode()
|
||||||
|
&& cached.wrap === currentWrap()
|
||||||
|
) {
|
||||||
|
return cached.html
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
const handleModeChange = (mode: DiffViewMode) => {
|
const handleModeChange = (mode: DiffViewMode) => {
|
||||||
|
if (compactDiffQuery()) {
|
||||||
|
setMobileModeOverride(mode)
|
||||||
|
}
|
||||||
params.setDiffViewMode(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 = () => {
|
const handleDiffRendered = () => {
|
||||||
if (!disableScrollTracking) {
|
if (!disableScrollTracking) {
|
||||||
params.handleScrollRendered()
|
params.handleScrollRendered()
|
||||||
@@ -95,41 +136,54 @@ export function createDiffContentRenderer(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
|
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
|
||||||
ref={registerRef}
|
data-diff-mode={currentMode()}
|
||||||
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
ref={registerRef}
|
||||||
>
|
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
||||||
|
>
|
||||||
<div class="tool-call-diff-toolbar" role="group" aria-label={params.t("toolCall.diff.viewMode.ariaLabel")}>
|
<div class="tool-call-diff-toolbar" role="group" aria-label={params.t("toolCall.diff.viewMode.ariaLabel")}>
|
||||||
<span class="tool-call-diff-toolbar-label">{toolbarLabel}</span>
|
<span class="tool-call-diff-toolbar-label">{toolbarLabel}</span>
|
||||||
<div class="tool-call-diff-toggle">
|
<div class="file-viewer-toolbar">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`tool-call-diff-mode-button${diffMode() === "split" ? " active" : ""}`}
|
class="file-viewer-toolbar-icon-button"
|
||||||
aria-pressed={diffMode() === "split"}
|
onClick={() => void copyToClipboard(payload.diffText)}
|
||||||
onClick={() => handleModeChange("split")}
|
aria-label={copyPatchTitle()}
|
||||||
|
title={copyPatchTitle()}
|
||||||
>
|
>
|
||||||
{params.t("toolCall.diff.viewMode.split")}
|
<Copy class="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`tool-call-diff-mode-button${diffMode() === "unified" ? " active" : ""}`}
|
class="file-viewer-toolbar-icon-button"
|
||||||
aria-pressed={diffMode() === "unified"}
|
onClick={() => handleModeChange(nextViewMode())}
|
||||||
onClick={() => handleModeChange("unified")}
|
aria-label={viewModeTitle()}
|
||||||
|
title={viewModeTitle()}
|
||||||
>
|
>
|
||||||
{params.t("toolCall.diff.viewMode.unified")}
|
{nextViewMode() === "split" ? <Split class="h-4 w-4" aria-hidden="true" /> : <AlignJustify class="h-4 w-4" aria-hidden="true" />}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={`file-viewer-toolbar-icon-button${wordWrapEnabled() ? " active" : ""}`}
|
||||||
|
onClick={() => setWordWrapEnabled((enabled) => !enabled)}
|
||||||
|
aria-label={wordWrapTitle()}
|
||||||
|
title={wordWrapTitle()}
|
||||||
|
>
|
||||||
|
<WrapText class="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{cachedHtml ? (
|
{cachedHtml() ? (
|
||||||
<CachedDiffMarkup html={cachedHtml} onRendered={handleDiffRendered} />
|
<CachedDiffMarkup html={cachedHtml()!} onRendered={handleDiffRendered} />
|
||||||
) : (
|
) : (
|
||||||
<Suspense fallback={<pre class="tool-call-diff-fallback">{payload.diffText}</pre>}>
|
<Suspense fallback={<pre class="tool-call-diff-fallback">{payload.diffText}</pre>}>
|
||||||
<LazyToolCallDiffViewer
|
<LazyToolCallDiffViewer
|
||||||
diffText={payload.diffText}
|
diffText={payload.diffText}
|
||||||
filePath={payload.filePath}
|
filePath={payload.filePath}
|
||||||
theme={themeKey}
|
theme={themeKey}
|
||||||
mode={diffMode()}
|
mode={currentMode()}
|
||||||
|
wrap={currentWrap()}
|
||||||
cacheEntryParams={cacheEntryParams as any}
|
cacheEntryParams={cacheEntryParams as any}
|
||||||
onRendered={handleDiffRendered}
|
onRendered={handleDiffRendered}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "Diff view mode",
|
"toolCall.diff.viewMode.ariaLabel": "Diff view mode",
|
||||||
"toolCall.diff.viewMode.split": "Split",
|
"toolCall.diff.viewMode.split": "Split",
|
||||||
"toolCall.diff.viewMode.unified": "Unified",
|
"toolCall.diff.viewMode.unified": "Unified",
|
||||||
|
"toolCall.diff.switchToSplit": "Switch to split view",
|
||||||
|
"toolCall.diff.switchToUnified": "Switch to unified view",
|
||||||
|
"toolCall.diff.enableWordWrap": "Enable word wrap",
|
||||||
|
"toolCall.diff.disableWordWrap": "Disable word wrap",
|
||||||
|
"toolCall.diff.copyPatch": "Copy patch",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "Diagnostics",
|
"toolCall.diagnostics.title": "Diagnostics",
|
||||||
"toolCall.diagnostics.ariaLabel": "Diagnostics",
|
"toolCall.diagnostics.ariaLabel": "Diagnostics",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "Modo de vista de diff",
|
"toolCall.diff.viewMode.ariaLabel": "Modo de vista de diff",
|
||||||
"toolCall.diff.viewMode.split": "Dividida",
|
"toolCall.diff.viewMode.split": "Dividida",
|
||||||
"toolCall.diff.viewMode.unified": "Unificada",
|
"toolCall.diff.viewMode.unified": "Unificada",
|
||||||
|
"toolCall.diff.switchToSplit": "Cambiar a vista dividida",
|
||||||
|
"toolCall.diff.switchToUnified": "Cambiar a vista unificada",
|
||||||
|
"toolCall.diff.enableWordWrap": "Activar ajuste de línea",
|
||||||
|
"toolCall.diff.disableWordWrap": "Desactivar ajuste de línea",
|
||||||
|
"toolCall.diff.copyPatch": "Copiar patch",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "Diagnósticos",
|
"toolCall.diagnostics.title": "Diagnósticos",
|
||||||
"toolCall.diagnostics.ariaLabel": "Diagnósticos",
|
"toolCall.diagnostics.ariaLabel": "Diagnósticos",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "Mode d'affichage du diff",
|
"toolCall.diff.viewMode.ariaLabel": "Mode d'affichage du diff",
|
||||||
"toolCall.diff.viewMode.split": "Côte à côte",
|
"toolCall.diff.viewMode.split": "Côte à côte",
|
||||||
"toolCall.diff.viewMode.unified": "Unifié",
|
"toolCall.diff.viewMode.unified": "Unifié",
|
||||||
|
"toolCall.diff.switchToSplit": "Passer à la vue côte à côte",
|
||||||
|
"toolCall.diff.switchToUnified": "Passer à la vue unifiée",
|
||||||
|
"toolCall.diff.enableWordWrap": "Activer le retour à la ligne",
|
||||||
|
"toolCall.diff.disableWordWrap": "Désactiver le retour à la ligne",
|
||||||
|
"toolCall.diff.copyPatch": "Copier le patch",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "Diagnostics",
|
"toolCall.diagnostics.title": "Diagnostics",
|
||||||
"toolCall.diagnostics.ariaLabel": "Diagnostics",
|
"toolCall.diagnostics.ariaLabel": "Diagnostics",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "מצב תצוגת diff",
|
"toolCall.diff.viewMode.ariaLabel": "מצב תצוגת diff",
|
||||||
"toolCall.diff.viewMode.split": "מפוצל",
|
"toolCall.diff.viewMode.split": "מפוצל",
|
||||||
"toolCall.diff.viewMode.unified": "מאוחד",
|
"toolCall.diff.viewMode.unified": "מאוחד",
|
||||||
|
"toolCall.diff.switchToSplit": "עבור לתצוגה מפוצלת",
|
||||||
|
"toolCall.diff.switchToUnified": "עבור לתצוגה מאוחדת",
|
||||||
|
"toolCall.diff.enableWordWrap": "הפעל גלישת מילים",
|
||||||
|
"toolCall.diff.disableWordWrap": "כבה גלישת מילים",
|
||||||
|
"toolCall.diff.copyPatch": "העתק patch",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "אבחון",
|
"toolCall.diagnostics.title": "אבחון",
|
||||||
"toolCall.diagnostics.ariaLabel": "אבחון",
|
"toolCall.diagnostics.ariaLabel": "אבחון",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "diff 表示モード",
|
"toolCall.diff.viewMode.ariaLabel": "diff 表示モード",
|
||||||
"toolCall.diff.viewMode.split": "分割",
|
"toolCall.diff.viewMode.split": "分割",
|
||||||
"toolCall.diff.viewMode.unified": "ユニファイド",
|
"toolCall.diff.viewMode.unified": "ユニファイド",
|
||||||
|
"toolCall.diff.switchToSplit": "分割表示に切り替え",
|
||||||
|
"toolCall.diff.switchToUnified": "ユニファイド表示に切り替え",
|
||||||
|
"toolCall.diff.enableWordWrap": "折り返しを有効化",
|
||||||
|
"toolCall.diff.disableWordWrap": "折り返しを無効化",
|
||||||
|
"toolCall.diff.copyPatch": "パッチをコピー",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "診断",
|
"toolCall.diagnostics.title": "診断",
|
||||||
"toolCall.diagnostics.ariaLabel": "診断",
|
"toolCall.diagnostics.ariaLabel": "診断",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "Режим просмотра diff",
|
"toolCall.diff.viewMode.ariaLabel": "Режим просмотра diff",
|
||||||
"toolCall.diff.viewMode.split": "Раздельный",
|
"toolCall.diff.viewMode.split": "Раздельный",
|
||||||
"toolCall.diff.viewMode.unified": "Единый",
|
"toolCall.diff.viewMode.unified": "Единый",
|
||||||
|
"toolCall.diff.switchToSplit": "Переключить на раздельный вид",
|
||||||
|
"toolCall.diff.switchToUnified": "Переключить на единый вид",
|
||||||
|
"toolCall.diff.enableWordWrap": "Включить перенос слов",
|
||||||
|
"toolCall.diff.disableWordWrap": "Выключить перенос слов",
|
||||||
|
"toolCall.diff.copyPatch": "Скопировать patch",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "Диагностика",
|
"toolCall.diagnostics.title": "Диагностика",
|
||||||
"toolCall.diagnostics.ariaLabel": "Диагностика",
|
"toolCall.diagnostics.ariaLabel": "Диагностика",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
|||||||
"toolCall.diff.viewMode.ariaLabel": "Diff 视图模式",
|
"toolCall.diff.viewMode.ariaLabel": "Diff 视图模式",
|
||||||
"toolCall.diff.viewMode.split": "分栏",
|
"toolCall.diff.viewMode.split": "分栏",
|
||||||
"toolCall.diff.viewMode.unified": "统一",
|
"toolCall.diff.viewMode.unified": "统一",
|
||||||
|
"toolCall.diff.switchToSplit": "切换到分栏视图",
|
||||||
|
"toolCall.diff.switchToUnified": "切换到统一视图",
|
||||||
|
"toolCall.diff.enableWordWrap": "启用自动换行",
|
||||||
|
"toolCall.diff.disableWordWrap": "禁用自动换行",
|
||||||
|
"toolCall.diff.copyPatch": "复制补丁",
|
||||||
|
|
||||||
"toolCall.diagnostics.title": "诊断",
|
"toolCall.diagnostics.title": "诊断",
|
||||||
"toolCall.diagnostics.ariaLabel": "诊断",
|
"toolCall.diagnostics.ariaLabel": "诊断",
|
||||||
|
|||||||
@@ -321,6 +321,7 @@
|
|||||||
|
|
||||||
.tool-call-diff-shell {
|
.tool-call-diff-shell {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
scrollbar-gutter: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-diff-viewer {
|
.tool-call-diff-viewer {
|
||||||
@@ -343,6 +344,8 @@
|
|||||||
.tool-call-diff-shell .tool-call-diff-viewer {
|
.tool-call-diff-shell .tool-call-diff-viewer {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-call-diff-toolbar-label {
|
.tool-call-diff-toolbar-label {
|
||||||
@@ -513,6 +516,84 @@
|
|||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content,
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-hunk-content,
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-old-content,
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-new-content {
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .diff-line-num {
|
||||||
|
padding-left: 1px !important;
|
||||||
|
padding-right: 1px !important;
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table-num-col {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .tool-call-diff-compact-line-number {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num,
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num,
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-hunk-action {
|
||||||
|
padding-left: 2px !important;
|
||||||
|
padding-right: 2px !important;
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num,
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num,
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num [data-line-num],
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num [data-line-num] {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
word-break: normal !important;
|
||||||
|
overflow-wrap: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-hunk-action {
|
||||||
|
padding-top: 1px !important;
|
||||||
|
padding-bottom: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-item {
|
||||||
|
padding-left: 1.1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-operator {
|
||||||
|
margin-left: -1.1em !important;
|
||||||
|
width: 0.9em !important;
|
||||||
|
min-width: 0.9em !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table-wrapper {
|
||||||
|
--diff-aside-width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-item {
|
||||||
|
padding-left: 1.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-operator {
|
||||||
|
margin-left: -1.5em !important;
|
||||||
|
width: 1.1em !important;
|
||||||
|
min-width: 1.1em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tool-call-markdown .markdown-code-block {
|
.tool-call-markdown .markdown-code-block {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface RenderCache {
|
|||||||
html: string
|
html: string
|
||||||
theme?: string
|
theme?: string
|
||||||
mode?: string
|
mode?: string
|
||||||
|
wrap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingPermissionState {
|
export interface PendingPermissionState {
|
||||||
|
|||||||
Reference in New Issue
Block a user