Reapply "fix(ui): support unified diff patch format in session changes viewer"
This reverts commit af6429162f.
This commit is contained in:
@@ -1,15 +1,17 @@
|
|||||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
||||||
import { loadMonaco } from "../../lib/monaco/setup"
|
import { loadMonaco } from "../../lib/monaco/setup"
|
||||||
import { getOrCreateTextModel } from "../../lib/monaco/model-cache"
|
import { getOrCreateTextModel } from "../../lib/monaco/model-cache"
|
||||||
import { inferMonacoLanguageId } from "../../lib/monaco/language"
|
import { inferMonacoLanguageId } from "../../lib/monaco/language"
|
||||||
import { ensureMonacoLanguageLoaded } from "../../lib/monaco/setup"
|
import { ensureMonacoLanguageLoaded } from "../../lib/monaco/setup"
|
||||||
import { useTheme } from "../../lib/theme"
|
import { useTheme } from "../../lib/theme"
|
||||||
|
import { parsePatchToBeforeAfter } from "../../lib/diff-utils"
|
||||||
|
|
||||||
interface MonacoDiffViewerProps {
|
interface MonacoDiffViewerProps {
|
||||||
scopeKey: string
|
scopeKey: string
|
||||||
path: string
|
path: string
|
||||||
before: string
|
patch?: string
|
||||||
after: string
|
before?: string
|
||||||
|
after?: string
|
||||||
viewMode?: "split" | "unified"
|
viewMode?: "split" | "unified"
|
||||||
contextMode?: "expanded" | "collapsed"
|
contextMode?: "expanded" | "collapsed"
|
||||||
wordWrap?: "on" | "off"
|
wordWrap?: "on" | "off"
|
||||||
@@ -23,6 +25,16 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) {
|
|||||||
let monaco: any = null
|
let monaco: any = null
|
||||||
const [ready, setReady] = createSignal(false)
|
const [ready, setReady] = createSignal(false)
|
||||||
|
|
||||||
|
const resolvedContent = createMemo(() => {
|
||||||
|
if (props.patch !== undefined && props.patch !== null) {
|
||||||
|
return parsePatchToBeforeAfter(props.patch)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
before: props.before ?? "",
|
||||||
|
after: props.after ?? "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const disposeEditor = () => {
|
const disposeEditor = () => {
|
||||||
try {
|
try {
|
||||||
diffEditor?.setModel(null as any)
|
diffEditor?.setModel(null as any)
|
||||||
@@ -115,11 +127,12 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!ready() || !monaco || !diffEditor) return
|
if (!ready() || !monaco || !diffEditor) return
|
||||||
const languageId = inferMonacoLanguageId(monaco, props.path)
|
const languageId = inferMonacoLanguageId(monaco, props.path)
|
||||||
|
const { before, after } = resolvedContent()
|
||||||
const beforeKey = `${props.scopeKey}:diff:${props.path}:before`
|
const beforeKey = `${props.scopeKey}:diff:${props.path}:before`
|
||||||
const afterKey = `${props.scopeKey}:diff:${props.path}:after`
|
const afterKey = `${props.scopeKey}:diff:${props.path}:after`
|
||||||
|
|
||||||
const original = getOrCreateTextModel({ monaco, cacheKey: beforeKey, value: props.before, languageId })
|
const original = getOrCreateTextModel({ monaco, cacheKey: beforeKey, value: before, languageId })
|
||||||
const modified = getOrCreateTextModel({ monaco, cacheKey: afterKey, value: props.after, languageId })
|
const modified = getOrCreateTextModel({ monaco, cacheKey: afterKey, value: after, languageId })
|
||||||
diffEditor.setModel({ original, modified })
|
diffEditor.setModel({ original, modified })
|
||||||
|
|
||||||
void ensureMonacoLanguageLoaded(languageId).then(() => {
|
void ensureMonacoLanguageLoaded(languageId).then(() => {
|
||||||
|
|||||||
@@ -115,23 +115,22 @@ const ChangesTab: Component<ChangesTabProps> = (props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(file) => (
|
{(file) => (
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div class="file-viewer-empty">
|
<div class="file-viewer-empty">
|
||||||
<span class="file-viewer-empty-text">{props.t("instanceInfo.loading")}</span>
|
<span class="file-viewer-empty-text">{props.t("instanceInfo.loading")}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<LazyMonacoDiffViewer
|
<LazyMonacoDiffViewer
|
||||||
scopeKey={scopeKey()}
|
scopeKey={scopeKey()}
|
||||||
path={String(file().file || "")}
|
path={String(file().file || "")}
|
||||||
before={String((file() as any).before || "")}
|
patch={String((file() as any).patch || "")}
|
||||||
after={String((file() as any).after || "")}
|
viewMode={props.diffViewMode()}
|
||||||
viewMode={props.diffViewMode()}
|
contextMode={props.diffContextMode()}
|
||||||
contextMode={props.diffContextMode()}
|
wordWrap={props.diffWordWrapMode()}
|
||||||
wordWrap={props.diffWordWrapMode()}
|
/>
|
||||||
/>
|
</Suspense>
|
||||||
</Suspense>
|
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const HUNK_PATTERN = /(^|\n)@@/m
|
|||||||
const FILE_MARKER_PATTERN = /(^|\n)(diff --git |--- |\+\+\+)/
|
const FILE_MARKER_PATTERN = /(^|\n)(diff --git |--- |\+\+\+)/
|
||||||
const BEGIN_PATCH_PATTERN = /^\*\*\* (Begin|End) Patch/
|
const BEGIN_PATCH_PATTERN = /^\*\*\* (Begin|End) Patch/
|
||||||
const UPDATE_FILE_PATTERN = /^\*\*\* Update File: (.+)$/
|
const UPDATE_FILE_PATTERN = /^\*\*\* Update File: (.+)$/
|
||||||
|
const HUNK_HEADER_PATTERN = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
|
||||||
|
|
||||||
function stripCodeFence(value: string): string {
|
function stripCodeFence(value: string): string {
|
||||||
const trimmed = value.trim()
|
const trimmed = value.trim()
|
||||||
@@ -48,3 +49,48 @@ export function isRenderableDiffText(raw?: string | null): raw is string {
|
|||||||
if (!normalized) return false
|
if (!normalized) return false
|
||||||
return HUNK_PATTERN.test(normalized)
|
return HUNK_PATTERN.test(normalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parsePatchToBeforeAfter(patch: string): { before: string; after: string } {
|
||||||
|
if (!patch || patch.trim().length === 0) {
|
||||||
|
return { before: "", after: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = patch.replace(/\r\n/g, "\n").split("\n")
|
||||||
|
const beforeLines: string[] = []
|
||||||
|
const afterLines: string[] = []
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff --git")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (HUNK_HEADER_PATTERN.test(line)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (line.startsWith("-") && !line.startsWith("---")) {
|
||||||
|
beforeLines.push(line.slice(1))
|
||||||
|
} else if (line.startsWith("+") && !line.startsWith("+++")) {
|
||||||
|
afterLines.push(line.slice(1))
|
||||||
|
} else if (line.startsWith(" ")) {
|
||||||
|
beforeLines.push(line.slice(1))
|
||||||
|
afterLines.push(line.slice(1))
|
||||||
|
} else if (line === "") {
|
||||||
|
beforeLines.push("")
|
||||||
|
afterLines.push("")
|
||||||
|
} else {
|
||||||
|
beforeLines.push(line)
|
||||||
|
afterLines.push(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (beforeLines.length > 0 && beforeLines[beforeLines.length - 1] === "") {
|
||||||
|
beforeLines.pop()
|
||||||
|
}
|
||||||
|
while (afterLines.length > 0 && afterLines[afterLines.length - 1] === "") {
|
||||||
|
afterLines.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
before: beforeLines.join("\n"),
|
||||||
|
after: afterLines.join("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import type {
|
|||||||
Provider as SDKProvider,
|
Provider as SDKProvider,
|
||||||
Model as SDKModel,
|
Model as SDKModel,
|
||||||
} from "@opencode-ai/sdk"
|
} from "@opencode-ai/sdk"
|
||||||
import type { SessionStatus as SDKSessionStatus } from "@opencode-ai/sdk/v2/client"
|
import type { SessionStatus as SDKSessionStatus, FileDiff } from "@opencode-ai/sdk/v2/client"
|
||||||
import type { FileDiff } from "@opencode-ai/sdk/v2/client"
|
|
||||||
|
|
||||||
// Export SDK types for external use
|
// Export SDK types for external use
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
Reference in New Issue
Block a user