diff options
| author | Adam <[email protected]> | 2025-12-31 09:23:24 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-31 09:23:24 -0600 |
| commit | 2ec6a21cc0018be6677e4cbad6bf48dbf8b37786 (patch) | |
| tree | 6ad3f27288f660b4bba43772075c5138764f25b3 /packages | |
| parent | ebf5ad25c5f5cdf42bcb93199d9913f260ebe767 (diff) | |
| download | opencode-2ec6a21cc0018be6677e4cbad6bf48dbf8b37786.tar.gz opencode-2ec6a21cc0018be6677e4cbad6bf48dbf8b37786.zip | |
feat(desktop): unified diff toggle
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/app/src/context/layout.tsx | 11 | ||||
| -rw-r--r-- | packages/app/src/pages/session.tsx | 5 | ||||
| -rw-r--r-- | packages/enterprise/src/routes/share/[shareID].tsx | 2 | ||||
| -rw-r--r-- | packages/ui/src/components/code.tsx | 4 | ||||
| -rw-r--r-- | packages/ui/src/components/diff-ssr.tsx | 2 | ||||
| -rw-r--r-- | packages/ui/src/components/diff.tsx | 17 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 18 | ||||
| -rw-r--r-- | packages/ui/src/context/worker-pool.tsx | 16 | ||||
| -rw-r--r-- | packages/ui/src/pierre/worker.ts | 47 |
9 files changed, 91 insertions, 31 deletions
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index e57f69f8f..156adc4ff 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -30,6 +30,8 @@ type SessionTabs = { export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean } +export type ReviewDiffStyle = "unified" | "split" + export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({ name: "Layout", init: () => { @@ -49,6 +51,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, review: { opened: true, + diffStyle: "split" as ReviewDiffStyle, }, session: { width: 600, @@ -156,6 +159,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, review: { opened: createMemo(() => store.review?.opened ?? true), + diffStyle: createMemo(() => store.review?.diffStyle ?? "split"), + setDiffStyle(diffStyle: ReviewDiffStyle) { + if (!store.review) { + setStore("review", { opened: true, diffStyle }) + return + } + setStore("review", "diffStyle", diffStyle) + }, open() { setStore("review", "opened", true) }, diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 6b9ff9e08..032a8375a 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -742,6 +742,8 @@ export default function Page() { <div class="relative h-full mt-6 overflow-y-auto no-scrollbar"> <SessionReview diffs={diffs()} + diffStyle={layout.review.diffStyle()} + onDiffStyleChange={layout.review.setDiffStyle} classes={{ root: "pb-32", header: "px-4", @@ -867,7 +869,8 @@ export default function Page() { container: "px-6", }} diffs={diffs()} - split + diffStyle={layout.review.diffStyle()} + onDiffStyleChange={layout.review.setDiffStyle} /> </div> </Tabs.Content> diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index 3a3d580b2..d300f8758 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -33,7 +33,7 @@ const ClientOnlyCode = clientOnly(() => import("@opencode-ai/ui/code").then((m) const ClientOnlyWorkerPoolProvider = clientOnly(() => import("@opencode-ai/ui/pierre/worker").then((m) => ({ default: (props: { children: any }) => ( - <WorkerPoolProvider pool={m.workerPool}>{props.children}</WorkerPoolProvider> + <WorkerPoolProvider pools={m.getWorkerPools()}>{props.children}</WorkerPoolProvider> ), })), ) diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index 77696faed..fda08260f 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -1,7 +1,7 @@ import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/diffs" import { ComponentProps, createEffect, createMemo, splitProps } from "solid-js" import { createDefaultOptions, styleVariables } from "../pierre" -import { workerPool } from "../pierre/worker" +import { getWorkerPool } from "../pierre/worker" export type CodeProps<T = {}> = FileOptions<T> & { file: FileContents @@ -21,7 +21,7 @@ export function Code<T>(props: CodeProps<T>) { ...createDefaultOptions<T>("unified"), ...others, }, - workerPool, + getWorkerPool("unified"), ), ) diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx index e367a4fbe..56a12c100 100644 --- a/packages/ui/src/components/diff-ssr.tsx +++ b/packages/ui/src/components/diff-ssr.tsx @@ -13,7 +13,7 @@ export function Diff<T>(props: SSRDiffProps<T>) { let container!: HTMLDivElement let fileDiffRef!: HTMLElement const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"]) - const workerPool = useWorkerPool() + const workerPool = useWorkerPool(props.diffStyle) let fileDiffInstance: FileDiff<T> | undefined const cleanupFunctions: Array<() => void> = [] diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx index 75dde0440..ff70cece9 100644 --- a/packages/ui/src/components/diff.tsx +++ b/packages/ui/src/components/diff.tsx @@ -1,7 +1,7 @@ import { FileDiff } from "@pierre/diffs" import { createEffect, createMemo, onCleanup, splitProps } from "solid-js" import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre" -import { workerPool } from "../pierre/worker" +import { getWorkerPool } from "../pierre/worker" // interface ThreadMetadata { // threadId: string @@ -20,26 +20,23 @@ export function Diff<T>(props: DiffProps<T>) { ...createDefaultOptions(props.diffStyle), ...others, }, - workerPool, + getWorkerPool(props.diffStyle), ), ) - const cleanupFunctions: Array<() => void> = [] - createEffect(() => { + const diff = fileDiff() container.innerHTML = "" - fileDiff().render({ + diff.render({ oldFile: local.before, newFile: local.after, lineAnnotations: local.annotations, containerWrapper: container, }) - }) - onCleanup(() => { - // Clean up FileDiff event handlers and dispose SolidJS components - fileDiff()?.cleanUp() - cleanupFunctions.forEach((dispose) => dispose()) + onCleanup(() => { + diff.cleanUp() + }) }) return <div data-component="diff" style={styleVariables} ref={container} /> diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 9162b5216..9e6c633f4 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -1,5 +1,6 @@ import { Accordion } from "./accordion" import { Button } from "./button" +import { RadioGroup } from "./radio-group" import { DiffChanges } from "./diff-changes" import { FileIcon } from "./file-icon" import { Icon } from "./icon" @@ -13,8 +14,12 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { Dynamic } from "solid-js/web" import { checksum } from "@opencode-ai/util/encode" +export type SessionReviewDiffStyle = "unified" | "split" + export interface SessionReviewProps { split?: boolean + diffStyle?: SessionReviewDiffStyle + onDiffStyleChange?: (diffStyle: SessionReviewDiffStyle) => void class?: string classList?: Record<string, boolean | undefined> classes?: { root?: string; header?: string; container?: string } @@ -28,6 +33,8 @@ export const SessionReview = (props: SessionReviewProps) => { open: props.diffs.length > 10 ? [] : props.diffs.map((d) => d.file), }) + const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified") + const handleChange = (open: string[]) => { setStore("open", open) } @@ -60,6 +67,15 @@ export const SessionReview = (props: SessionReviewProps) => { > <div data-slot="session-review-title">Session changes</div> <div data-slot="session-review-actions"> + <Show when={props.onDiffStyleChange}> + <RadioGroup + options={["unified", "split"] as const} + current={diffStyle()} + value={(style) => style} + label={(style) => (style === "unified" ? "Unified" : "Split")} + onSelect={(style) => style && props.onDiffStyleChange?.(style)} + /> + </Show> <Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}> <Switch> <Match when={store.open.length > 0}>Collapse all</Match> @@ -102,7 +118,7 @@ export const SessionReview = (props: SessionReviewProps) => { <Dynamic component={diffComponent} preloadedDiff={diff.preloaded} - diffStyle={props.split ? "split" : "unified"} + diffStyle={diffStyle()} before={{ name: diff.file!, contents: diff.before!, diff --git a/packages/ui/src/context/worker-pool.tsx b/packages/ui/src/context/worker-pool.tsx index fc2eecc03..5f788f786 100644 --- a/packages/ui/src/context/worker-pool.tsx +++ b/packages/ui/src/context/worker-pool.tsx @@ -1,10 +1,20 @@ import type { WorkerPoolManager } from "@pierre/diffs/worker" import { createSimpleContext } from "./helper" -const ctx = createSimpleContext<WorkerPoolManager | undefined, { pool: WorkerPoolManager | undefined }>({ +export type WorkerPools = { + unified: WorkerPoolManager | undefined + split: WorkerPoolManager | undefined +} + +const ctx = createSimpleContext<WorkerPools, { pools: WorkerPools }>({ name: "WorkerPool", - init: (props) => props.pool, + init: (props) => props.pools, }) export const WorkerPoolProvider = ctx.provider -export const useWorkerPool = ctx.use + +export function useWorkerPool(diffStyle: "unified" | "split" | undefined) { + const pools = ctx.use() + if (diffStyle === "split") return pools.split + return pools.unified +} diff --git a/packages/ui/src/pierre/worker.ts b/packages/ui/src/pierre/worker.ts index 2d2640674..0d117c368 100644 --- a/packages/ui/src/pierre/worker.ts +++ b/packages/ui/src/pierre/worker.ts @@ -1,16 +1,15 @@ -import { getOrCreateWorkerPoolSingleton, WorkerPoolManager } from "@pierre/diffs/worker" +import { WorkerPoolManager } from "@pierre/diffs/worker" import ShikiWorkerUrl from "@pierre/diffs/worker/worker.js?worker&url" +export type WorkerPoolStyle = "unified" | "split" + export function workerFactory(): Worker { return new Worker(ShikiWorkerUrl, { type: "module" }) } -export const workerPool: WorkerPoolManager | undefined = (() => { - if (typeof window === "undefined") { - return undefined - } - return getOrCreateWorkerPoolSingleton({ - poolOptions: { +function createPool(lineDiffType: "none" | "word-alt") { + const pool = new WorkerPoolManager( + { workerFactory, // poolSize defaults to 8. More workers = more parallelism but // also more memory. Too many can actually slow things down. @@ -19,10 +18,34 @@ export const workerPool: WorkerPoolManager | undefined = (() => { // boot up time for workers poolSize: 2, }, - highlighterOptions: { + { theme: "OpenCode", - // Optionally preload languages to avoid lazy-loading delays - // langs: ["typescript", "javascript", "css", "html"], + lineDiffType, }, - }) -})() + ) + + pool.initialize() + return pool +} + +let unified: WorkerPoolManager | undefined +let split: WorkerPoolManager | undefined + +export function getWorkerPool(style: WorkerPoolStyle | undefined): WorkerPoolManager | undefined { + if (typeof window === "undefined") return + + if (style === "split") { + if (!split) split = createPool("word-alt") + return split + } + + if (!unified) unified = createPool("none") + return unified +} + +export function getWorkerPools() { + return { + unified: getWorkerPool("unified"), + split: getWorkerPool("split"), + } +} |
