Implement shared compact split and unified tool-call diff layout (#270)
# PR Title Implement shared compact split and unified tool-call diff layout --- Fixes #268 # PR Description ## Summary This PR makes tool-call diffs more compact in both `Unified` and `Split` views by reducing wasted horizontal space in line-number gutters and content indentation. ## What changed - introduced a shared compact-diff framework for tool-call diffs - kept mobile-specific policy limited to: - forcing unified mode below the breakpoint - enabling wrap only in mobile unified mode - added mode-specific compact applicators in the diff viewer: - unified applicator - split applicator - reduced gutter width waste by measuring rendered line-number text and tightening column width around it - removed unnecessary right-side content padding - aligned `+` / `-` markers closer to the left edge across both views - simplified cleanup after gatekeeper review by removing extra plumbing and residue ## Screenshots ### Before <img width="581" height="341" alt="image" src="https://github.com/user-attachments/assets/ec47b256-749a-4afc-8879-aaf33f0b46b6" /> ### After <img width="470" height="586" alt="image" src="https://github.com/user-attachments/assets/7258a5a2-47c4-408d-84bc-1b497761c7ad" /> ## Architectural approach This change intentionally uses: - shared policy in `packages/ui/src/components/tool-call/diff-render.tsx` - shared helper/measurement logic in `packages/ui/src/components/diff-viewer.tsx` - mode-specific applicators where unified and split DOM differ - CSS for shared visual spacing and alignment cleanup The goal was to keep the implementation architecturally clean and avoid building separate duplicated compact-diff features for: - mobile vs desktop - unified vs split Instead, the feature shares one compact-diff concept and only diverges where the upstream diff DOM requires separate handling. ## Files changed - `packages/ui/src/components/tool-call/diff-render.tsx` - `packages/ui/src/components/diff-viewer.tsx` - `packages/ui/src/styles/messaging/tool-call.css` - `packages/ui/src/types/message.ts` ## Validation Manual validation was performed in the running UI. Verified manually: - compact unified gutters on mobile - compact unified gutters on desktop - compact split gutters on desktop - tighter operator alignment in both modes Also verified: - `npm run typecheck` passes ## Notes - This PR is intended to address the compact diff layout problem described in the related issue. - Diff-specific CSS still lives in `tool-call.css`; future extraction into a smaller dedicated stylesheet is possible but not required for this change. --------- Co-authored-by: Shantur Rathore <i@shantur.com>
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 "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||
import { disableCache } from "@git-diff-view/core"
|
||||
@@ -20,6 +20,7 @@ interface ToolCallDiffViewerProps {
|
||||
filePath?: string
|
||||
theme: "light" | "dark"
|
||||
mode: DiffViewMode
|
||||
wrap?: boolean
|
||||
onRendered?: () => void
|
||||
cachedHtml?: string
|
||||
cacheEntryParams?: CacheEntryParams
|
||||
@@ -31,11 +32,183 @@ type DiffData = {
|
||||
hunks: string[]
|
||||
}
|
||||
|
||||
type CaptureContext = {
|
||||
theme: ToolCallDiffViewerProps["theme"]
|
||||
mode: DiffViewMode
|
||||
diffText: string
|
||||
cacheEntryParams?: CacheEntryParams
|
||||
function measureTextWidth(container: HTMLElement, text: string, source: HTMLElement) {
|
||||
const computed = window.getComputedStyle(source)
|
||||
const probe = document.createElement("span")
|
||||
probe.textContent = text || ""
|
||||
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) {
|
||||
@@ -67,12 +240,15 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
const contextKey = createMemo(() => {
|
||||
const data = diffData()
|
||||
if (!data) return ""
|
||||
return `${props.theme}|${props.mode}|${props.diffText}`
|
||||
return `${props.theme}|${props.mode}|${props.wrap ? "wrap" : "nowrap"}|${props.diffText}`
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const cachedHtml = props.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
|
||||
// and simply notify once rendered.
|
||||
props.onRendered?.()
|
||||
@@ -83,9 +259,10 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
if (!key) return
|
||||
if (!diffContainerRef) return
|
||||
if (lastCapturedKey === key) return
|
||||
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!diffContainerRef) return
|
||||
applyCompactDiffLayout(diffContainerRef, props.mode, Boolean(props.wrap))
|
||||
const markup = diffContainerRef.innerHTML
|
||||
if (!markup) return
|
||||
lastCapturedKey = key
|
||||
@@ -95,6 +272,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
html: markup,
|
||||
theme: props.theme,
|
||||
mode: props.mode,
|
||||
wrap: props.wrap,
|
||||
})
|
||||
}
|
||||
props.onRendered?.()
|
||||
@@ -122,7 +300,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
|
||||
diffViewTheme={props.theme}
|
||||
diffViewHighlight
|
||||
diffViewWrap={false}
|
||||
diffViewWrap={Boolean(props.wrap)}
|
||||
diffViewFontSize={13}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
@@ -131,7 +309,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div innerHTML={props.cachedHtml} />
|
||||
<div ref={diffContainerRef} innerHTML={props.cachedHtml} />
|
||||
</Show>
|
||||
</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 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 })),
|
||||
@@ -43,6 +46,16 @@ export function createDiffContentRenderer(params: {
|
||||
handleScrollRendered: () => 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) => {
|
||||
params.scrollHelpers.registerContainer(element)
|
||||
}
|
||||
@@ -58,7 +71,12 @@ export function createDiffContentRenderer(params: {
|
||||
: params.t("toolCall.diff.label"))
|
||||
const selectedVariant = options?.variant === "permission-diff" ? "permission-diff" : "diff"
|
||||
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 state = params.toolState()
|
||||
const disableScrollTracking = Boolean(
|
||||
@@ -76,17 +94,40 @@ export function createDiffContentRenderer(params: {
|
||||
}
|
||||
})()
|
||||
|
||||
let cachedHtml: string | undefined
|
||||
const cached = getCacheEntry<RenderCache>(cacheEntryParams)
|
||||
const currentMode = diffMode()
|
||||
if (cached && cached.text === payload.diffText && cached.theme === themeKey && cached.mode === currentMode) {
|
||||
cachedHtml = cached.html
|
||||
}
|
||||
const currentMode = createMemo(() => effectiveMode())
|
||||
const currentWrap = createMemo(() => shouldWrap())
|
||||
const cachedHtml = createMemo(() => {
|
||||
const cached = getCacheEntry<RenderCache>(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 = () => {
|
||||
if (!disableScrollTracking) {
|
||||
params.handleScrollRendered()
|
||||
@@ -95,41 +136,54 @@ export function createDiffContentRenderer(params: {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
|
||||
ref={registerRef}
|
||||
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
||||
>
|
||||
<div
|
||||
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
|
||||
data-diff-mode={currentMode()}
|
||||
ref={registerRef}
|
||||
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
||||
>
|
||||
<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>
|
||||
<div class="tool-call-diff-toggle">
|
||||
<div class="file-viewer-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
class={`tool-call-diff-mode-button${diffMode() === "split" ? " active" : ""}`}
|
||||
aria-pressed={diffMode() === "split"}
|
||||
onClick={() => handleModeChange("split")}
|
||||
class="file-viewer-toolbar-icon-button"
|
||||
onClick={() => void copyToClipboard(payload.diffText)}
|
||||
aria-label={copyPatchTitle()}
|
||||
title={copyPatchTitle()}
|
||||
>
|
||||
{params.t("toolCall.diff.viewMode.split")}
|
||||
<Copy class="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`tool-call-diff-mode-button${diffMode() === "unified" ? " active" : ""}`}
|
||||
aria-pressed={diffMode() === "unified"}
|
||||
onClick={() => handleModeChange("unified")}
|
||||
class="file-viewer-toolbar-icon-button"
|
||||
onClick={() => handleModeChange(nextViewMode())}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
{cachedHtml ? (
|
||||
<CachedDiffMarkup html={cachedHtml} onRendered={handleDiffRendered} />
|
||||
{cachedHtml() ? (
|
||||
<CachedDiffMarkup html={cachedHtml()!} onRendered={handleDiffRendered} />
|
||||
) : (
|
||||
<Suspense fallback={<pre class="tool-call-diff-fallback">{payload.diffText}</pre>}>
|
||||
<LazyToolCallDiffViewer
|
||||
diffText={payload.diffText}
|
||||
filePath={payload.filePath}
|
||||
theme={themeKey}
|
||||
mode={diffMode()}
|
||||
mode={currentMode()}
|
||||
wrap={currentWrap()}
|
||||
cacheEntryParams={cacheEntryParams as any}
|
||||
onRendered={handleDiffRendered}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "Diff view mode",
|
||||
"toolCall.diff.viewMode.split": "Split",
|
||||
"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.ariaLabel": "Diagnostics",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "Modo de vista de diff",
|
||||
"toolCall.diff.viewMode.split": "Dividida",
|
||||
"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.ariaLabel": "Diagnósticos",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "Mode d'affichage du diff",
|
||||
"toolCall.diff.viewMode.split": "Côte à côte",
|
||||
"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.ariaLabel": "Diagnostics",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "מצב תצוגת diff",
|
||||
"toolCall.diff.viewMode.split": "מפוצל",
|
||||
"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.ariaLabel": "אבחון",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "diff 表示モード",
|
||||
"toolCall.diff.viewMode.split": "分割",
|
||||
"toolCall.diff.viewMode.unified": "ユニファイド",
|
||||
"toolCall.diff.switchToSplit": "分割表示に切り替え",
|
||||
"toolCall.diff.switchToUnified": "ユニファイド表示に切り替え",
|
||||
"toolCall.diff.enableWordWrap": "折り返しを有効化",
|
||||
"toolCall.diff.disableWordWrap": "折り返しを無効化",
|
||||
"toolCall.diff.copyPatch": "パッチをコピー",
|
||||
|
||||
"toolCall.diagnostics.title": "診断",
|
||||
"toolCall.diagnostics.ariaLabel": "診断",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "Режим просмотра diff",
|
||||
"toolCall.diff.viewMode.split": "Раздельный",
|
||||
"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.ariaLabel": "Диагностика",
|
||||
|
||||
@@ -18,6 +18,11 @@ export const toolCallMessages = {
|
||||
"toolCall.diff.viewMode.ariaLabel": "Diff 视图模式",
|
||||
"toolCall.diff.viewMode.split": "分栏",
|
||||
"toolCall.diff.viewMode.unified": "统一",
|
||||
"toolCall.diff.switchToSplit": "切换到分栏视图",
|
||||
"toolCall.diff.switchToUnified": "切换到统一视图",
|
||||
"toolCall.diff.enableWordWrap": "启用自动换行",
|
||||
"toolCall.diff.disableWordWrap": "禁用自动换行",
|
||||
"toolCall.diff.copyPatch": "复制补丁",
|
||||
|
||||
"toolCall.diagnostics.title": "诊断",
|
||||
"toolCall.diagnostics.ariaLabel": "诊断",
|
||||
|
||||
@@ -321,6 +321,7 @@
|
||||
|
||||
.tool-call-diff-shell {
|
||||
padding: 0;
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
.tool-call-diff-viewer {
|
||||
@@ -343,6 +344,8 @@
|
||||
.tool-call-diff-shell .tool-call-diff-viewer {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.tool-call-diff-toolbar-label {
|
||||
@@ -513,6 +516,84 @@
|
||||
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 {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface RenderCache {
|
||||
html: string
|
||||
theme?: string
|
||||
mode?: string
|
||||
wrap?: boolean
|
||||
}
|
||||
|
||||
export interface PendingPermissionState {
|
||||
|
||||
Reference in New Issue
Block a user