mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-30 06:02:11 +00:00
fix(session-ui): enable word diffs in unified view (#45833)
This commit is contained in:
parent
92b9eebab2
commit
bb390f435c
7 changed files with 130 additions and 17 deletions
35
packages/session-ui/component-tests/file.spec.ts
Normal file
35
packages/session-ui/component-tests/file.spec.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { expect, story } from "../../storybook/playwright/story"
|
||||
|
||||
for (const split of [false, true]) {
|
||||
for (const theme of ["light", "dark"]) {
|
||||
story(`highlights changed words in ${split ? "split" : "unified"} ${theme} diffs`, async ({ mount }) => {
|
||||
const root = await mount("components-session-review--inline-changes", { args: { split }, globals: { theme } })
|
||||
const diffs = root.locator("diffs-container")
|
||||
await expect(diffs).toHaveCount(2)
|
||||
for (const diff of await diffs.all()) {
|
||||
await expect(diff.locator('[data-line] [style*="--syntax-"]')).not.toHaveCount(0)
|
||||
}
|
||||
const additions = root.locator('[data-line-type="change-addition"] [data-diff-span]')
|
||||
await expect(additions).toHaveText(["select-text"])
|
||||
await expect(root.locator('[data-line-type="context"] [data-diff-span]')).toHaveCount(0)
|
||||
await expect(root.locator('[data-line-type="change-deletion"] [data-diff-span]')).toHaveText([
|
||||
'"http" in',
|
||||
"? error.reason.",
|
||||
"response?.",
|
||||
": undefined",
|
||||
])
|
||||
await expect(additions).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)")
|
||||
})
|
||||
}
|
||||
|
||||
for (const source of ["files", "metadata"]) {
|
||||
story(`skips word diffs for large ${split ? "split" : "unified"} ${source}`, async ({ mount }) => {
|
||||
const root = await mount("components-session-review--large-file", { args: { split, source } })
|
||||
const line = root.locator('[data-line][data-line-type="change-addition"]')
|
||||
await expect(line).toHaveText("export const value = 'after'")
|
||||
// Plain first paint is not proof that the worker kept inline diffs disabled.
|
||||
await expect(line.locator('[style*="--syntax-"]')).not.toHaveCount(0)
|
||||
await expect(root.locator("[data-diff-span]")).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { expect, story } from "../../storybook/playwright/story"
|
|||
story("opens the comment editor when code is clicked", async ({ mount }) => {
|
||||
const root = await mount("components-session-review--interactive-comments")
|
||||
const review = root.locator('[data-component="session-review"]')
|
||||
await review.getByText("export const value = 'after'", { exact: true }).click()
|
||||
await review.locator('[data-line-type="change-addition"] [data-diff-span]').click()
|
||||
await expect(review.getByRole("textbox")).toBeVisible()
|
||||
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -906,7 +906,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||
|
||||
createEffect(() => {
|
||||
const opts = options()
|
||||
const workerPool = getWorkerPool("unified")
|
||||
const workerPool = getWorkerPool()
|
||||
const virtualizer = virtuals.get()
|
||||
|
||||
renderViewer({
|
||||
|
|
@ -1111,7 +1111,8 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||
|
||||
createEffect(() => {
|
||||
const opts = options()
|
||||
const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
|
||||
// Worker render options override per-viewer options, including the large-file fallback.
|
||||
const workerPool = getWorkerPool(large() ? "none" : "word-alt")
|
||||
const virtualizer = virtuals.get()
|
||||
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
||||
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { createStore } from "solid-js/store"
|
||||
import { parseDiffFromFile } from "@pierre/diffs"
|
||||
import { CurrentSessionProviders } from "../storybook/current-session-story"
|
||||
import { editThenTestDocument, reviewDiffs } from "../storybook/current-session-fixtures"
|
||||
import { File } from "./file"
|
||||
import { SessionReview, type SessionReviewComment } from "./session-review"
|
||||
|
||||
function ReviewStory(props: { split?: boolean }) {
|
||||
|
|
@ -77,3 +79,79 @@ function InteractiveCommentsStory() {
|
|||
}
|
||||
|
||||
export const InteractiveComments = { render: () => <InteractiveCommentsStory /> }
|
||||
|
||||
const gitDiffs = [
|
||||
{
|
||||
// OpenCode 93e1f383dd79683af4fc5ad139cea0516603c838, unchanged git-show output.
|
||||
file: "packages/session-ui/src/components/file.tsx",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
patch: `diff --git a/packages/session-ui/src/components/file.tsx b/packages/session-ui/src/components/file.tsx
|
||||
index 704971b014..4876731cbd 100644
|
||||
--- a/packages/session-ui/src/components/file.tsx
|
||||
+++ b/packages/session-ui/src/components/file.tsx
|
||||
@@ -702,7 +702,7 @@ function ViewerShell(props: {
|
||||
data-mode={props.mode}
|
||||
dir="ltr"
|
||||
style={styleVariables}
|
||||
- class="relative outline-none"
|
||||
+ class="relative select-text outline-none"
|
||||
classList={{
|
||||
...props.classList,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
`,
|
||||
},
|
||||
{
|
||||
// OpenCode 497a24c17d, unchanged git-show output.
|
||||
file: "packages/core/src/session/runner/retry.ts",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
patch: `diff --git a/packages/core/src/session/runner/retry.ts b/packages/core/src/session/runner/retry.ts
|
||||
index 10f6680097..ef26792ffe 100644
|
||||
--- a/packages/core/src/session/runner/retry.ts
|
||||
+++ b/packages/core/src/session/runner/retry.ts
|
||||
@@ -15,7 +15,7 @@ export interface Input {
|
||||
}
|
||||
\x20
|
||||
export function isRetryable(error: AIError) {
|
||||
- const override = "http" in error.reason ? error.reason.http?.response?.headers["x-should-retry"] : undefined
|
||||
+ const override = error.reason.http?.headers["x-should-retry"]
|
||||
if (override === "true") return true
|
||||
if (override === "false") return false
|
||||
switch (error.reason._tag) {
|
||||
`,
|
||||
},
|
||||
]
|
||||
|
||||
export const InlineChanges = {
|
||||
args: { split: false },
|
||||
render: (args: { split: boolean }) => (
|
||||
<CurrentSessionProviders document={editThenTestDocument}>
|
||||
<div class="mx-auto h-screen min-h-[620px] w-full max-w-[1100px] overflow-hidden bg-background-base">
|
||||
<SessionReview
|
||||
title="OpenCode Git history"
|
||||
diffs={gitDiffs}
|
||||
open={gitDiffs.map((diff) => diff.file)}
|
||||
split={args.split}
|
||||
/>
|
||||
</div>
|
||||
</CurrentSessionProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const LargeFile = {
|
||||
args: { split: false, source: "files" },
|
||||
argTypes: { source: { control: "select", options: ["files", "metadata"] } },
|
||||
render: (args: { split: boolean; source: string }) => {
|
||||
// Cross the viewer's 500,000-character limit without long changed lines or 1,000 total lines.
|
||||
const padding = `// ${"unchanged ".repeat(70)}\n`.repeat(800)
|
||||
const before = { name: "large.ts", contents: `export const value = 'before'\n${padding}` }
|
||||
const after = { name: "large.ts", contents: `export const value = 'after'\n${padding}` }
|
||||
const input = args.source === "metadata" ? { fileDiff: parseDiffFromFile(before, after) } : { before, after }
|
||||
return (
|
||||
<div class="h-screen overflow-auto bg-background-base">
|
||||
<File mode="diff" {...input} diffStyle={args.split ? "split" : "unified"} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"])
|
|||
disableBackground: false,
|
||||
expansionLineCount: 20,
|
||||
hunkSeparators: "line-info-basic",
|
||||
lineDiffType: style === "split" ? "word-alt" : "none",
|
||||
lineDiffType: "word-alt",
|
||||
maxLineDiffLength: 1000,
|
||||
maxLineLengthForHighlighting: 1000,
|
||||
disableFileHeader: true,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import { registerOpenCodeTheme } from "@opencode-ai/ui/context/marked-theme-regi
|
|||
|
||||
registerOpenCodeTheme()
|
||||
|
||||
export type WorkerPoolStyle = "unified" | "split"
|
||||
|
||||
export function workerFactory(): Worker {
|
||||
return new Worker(ShikiWorkerUrl, { type: "module" })
|
||||
}
|
||||
|
|
@ -32,24 +30,25 @@ function createPool(lineDiffType: "none" | "word-alt") {
|
|||
return pool
|
||||
}
|
||||
|
||||
let unified: WorkerPoolManager | undefined
|
||||
let split: WorkerPoolManager | undefined
|
||||
let plain: WorkerPoolManager | undefined
|
||||
let diff: WorkerPoolManager | undefined
|
||||
|
||||
export function getWorkerPool(style: WorkerPoolStyle | undefined): WorkerPoolManager | undefined {
|
||||
export function getWorkerPool(lineDiffType: "none" | "word-alt" = "word-alt"): WorkerPoolManager | undefined {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
if (style === "split") {
|
||||
if (!split) split = createPool("word-alt")
|
||||
return split
|
||||
if (lineDiffType === "none") {
|
||||
if (!plain) plain = createPool("none")
|
||||
return plain
|
||||
}
|
||||
|
||||
if (!unified) unified = createPool("none")
|
||||
return unified
|
||||
if (!diff) diff = createPool("word-alt")
|
||||
return diff
|
||||
}
|
||||
|
||||
export function getWorkerPools() {
|
||||
const pool = getWorkerPool()
|
||||
return {
|
||||
unified: getWorkerPool("unified"),
|
||||
split: getWorkerPool("split"),
|
||||
unified: pool,
|
||||
split: pool,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
|
|||
const locale = useLocale()
|
||||
|
||||
createEffect(() => {
|
||||
getWorkerPool(props.diffStyle)
|
||||
getWorkerPool()
|
||||
})
|
||||
|
||||
const fileIndex = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue