| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- import type { FileDiff } from "@opencode-ai/sdk"
- import { createMemo, For, Match, Show, Switch } from "solid-js"
- export function DiffChanges(props: { diff: FileDiff | FileDiff[]; variant?: "default" | "bars" }) {
- const variant = () => props.variant ?? "default"
- const additions = createMemo(() =>
- Array.isArray(props.diff)
- ? props.diff.reduce((acc, diff) => acc + (diff.additions ?? 0), 0)
- : props.diff.additions,
- )
- const deletions = createMemo(() =>
- Array.isArray(props.diff)
- ? props.diff.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0)
- : props.diff.deletions,
- )
- const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0))
- const countLines = (text: string) => {
- if (!text) return 0
- return text.split("\n").length
- }
- const totalBeforeLines = createMemo(() => {
- if (!Array.isArray(props.diff)) return countLines(props.diff.before || "")
- return props.diff.reduce((acc, diff) => acc + countLines(diff.before || ""), 0)
- })
- const blockCounts = createMemo(() => {
- const TOTAL_BLOCKS = 5
- const adds = additions() ?? 0
- const dels = deletions() ?? 0
- if (adds === 0 && dels === 0) {
- return { added: 0, deleted: 0, neutral: TOTAL_BLOCKS }
- }
- const total = adds + dels
- if (total < 5) {
- const added = adds > 0 ? 1 : 0
- const deleted = dels > 0 ? 1 : 0
- const neutral = TOTAL_BLOCKS - added - deleted
- return { added, deleted, neutral }
- }
- const ratio = adds > dels ? adds / dels : dels / adds
- let BLOCKS_FOR_COLORS = TOTAL_BLOCKS
- if (total < 20) {
- BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1
- } else if (ratio < 4) {
- BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1
- }
- const percentAdded = adds / total
- const percentDeleted = dels / total
- const added_raw = percentAdded * BLOCKS_FOR_COLORS
- const deleted_raw = percentDeleted * BLOCKS_FOR_COLORS
- let added = adds > 0 ? Math.max(1, Math.round(added_raw)) : 0
- let deleted = dels > 0 ? Math.max(1, Math.round(deleted_raw)) : 0
- // Cap bars based on actual change magnitude
- if (adds > 0 && adds <= 5) added = Math.min(added, 1)
- if (adds > 5 && adds <= 10) added = Math.min(added, 2)
- if (dels > 0 && dels <= 5) deleted = Math.min(deleted, 1)
- if (dels > 5 && dels <= 10) deleted = Math.min(deleted, 2)
- let total_allocated = added + deleted
- if (total_allocated > BLOCKS_FOR_COLORS) {
- if (added_raw > deleted_raw) {
- added = BLOCKS_FOR_COLORS - deleted
- } else {
- deleted = BLOCKS_FOR_COLORS - added
- }
- total_allocated = added + deleted
- }
- const neutral = Math.max(0, TOTAL_BLOCKS - total_allocated)
- return { added, deleted, neutral }
- })
- const ADD_COLOR = "var(--icon-diff-add-base)"
- const DELETE_COLOR = "var(--icon-diff-delete-base)"
- const NEUTRAL_COLOR = "var(--icon-weak-base)"
- const visibleBlocks = createMemo(() => {
- const counts = blockCounts()
- const blocks = [
- ...Array(counts.added).fill(ADD_COLOR),
- ...Array(counts.deleted).fill(DELETE_COLOR),
- ...Array(counts.neutral).fill(NEUTRAL_COLOR),
- ]
- return blocks.slice(0, 5)
- })
- return (
- <Show when={variant() === "default" ? total() > 0 : true}>
- <div data-component="diff-changes" data-variant={variant()}>
- <Switch>
- <Match when={variant() === "bars"}>
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
- <g>
- <For each={visibleBlocks()}>
- {(color, i) => <rect x={i() * 4} width="2" height="12" rx="1" fill={color} />}
- </For>
- </g>
- </svg>
- </Match>
- <Match when={variant() === "default"}>
- <span data-slot="additions">{`+${additions()}`}</span>
- <span data-slot="deletions">{`-${deletions()}`}</span>
- </Match>
- </Switch>
- </div>
- </Show>
- )
- }
|