| ... | ... | @@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac |
| 3 | 3 | import { useQuery } from "@tanstack/react-query"; |
| 4 | 4 | import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; |
| 5 | 5 | import { ThreadId, type TurnId } from "@t3tools/contracts"; |
| 6 | | import { ChevronLeftIcon, ChevronRightIcon, Columns2Icon, Rows3Icon } from "lucide-react"; |
| 6 | import { |
| 7 | ChevronLeftIcon, |
| 8 | ChevronRightIcon, |
| 9 | Columns2Icon, |
| 10 | Rows3Icon, |
| 11 | TextWrapIcon, |
| 12 | } from "lucide-react"; |
| 7 | 13 | import { |
| 8 | 14 | type WheelEvent as ReactWheelEvent, |
| 9 | 15 | useCallback, |
| ... | ... | @@ -162,8 +168,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 162 | 168 | const { resolvedTheme } = useTheme(); |
| 163 | 169 | const { settings } = useAppSettings(); |
| 164 | 170 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 171 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 165 | 172 | const patchViewportRef = useRef<HTMLDivElement>(null); |
| 166 | 173 | const turnStripRef = useRef<HTMLDivElement>(null); |
| 174 | const previousDiffOpenRef = useRef(false); |
| 167 | 175 | const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); |
| 168 | 176 | const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); |
| 169 | 177 | const routeThreadId = useParams({ |
| ... | ... | @@ -171,6 +179,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 171 | 179 | select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null), |
| 172 | 180 | }); |
| 173 | 181 | const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); |
| 182 | const diffOpen = diffSearch.diff === "1"; |
| 174 | 183 | const activeThreadId = routeThreadId; |
| 175 | 184 | const activeThread = useStore((store) => |
| 176 | 185 | activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined, |
| ... | ... | @@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 293 | 302 | ); |
| 294 | 303 | }, [renderablePatch]); |
| 295 | 304 | |
| 305 | useEffect(() => { |
| 306 | if (diffOpen && !previousDiffOpenRef.current) { |
| 307 | setDiffWordWrap(settings.diffWordWrap); |
| 308 | } |
| 309 | previousDiffOpenRef.current = diffOpen; |
| 310 | }, [diffOpen, settings.diffWordWrap]); |
| 311 | |
| 296 | 312 | useEffect(() => { |
| 297 | 313 | if (!selectedFilePath || !patchViewportRef.current) { |
| 298 | 314 | return; |
| ... | ... | @@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 490 | 506 | ))} |
| 491 | 507 | </div> |
| 492 | 508 | </div> |
| 493 | | <ToggleGroup |
| 494 | | className="shrink-0 [-webkit-app-region:no-drag]" |
| 495 | | variant="outline" |
| 496 | | size="xs" |
| 497 | | value={[diffRenderMode]} |
| 498 | | onValueChange={(value) => { |
| 499 | | const next = value[0]; |
| 500 | | if (next === "stacked" || next === "split") { |
| 501 | | setDiffRenderMode(next); |
| 502 | | } |
| 503 | | }} |
| 504 | | > |
| 505 | | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 506 | | <Rows3Icon className="size-3" /> |
| 507 | | </Toggle> |
| 508 | | <Toggle aria-label="Split diff view" value="split"> |
| 509 | | <Columns2Icon className="size-3" /> |
| 509 | <div className="flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]"> |
| 510 | <ToggleGroup |
| 511 | className="shrink-0" |
| 512 | variant="outline" |
| 513 | size="xs" |
| 514 | value={[diffRenderMode]} |
| 515 | onValueChange={(value) => { |
| 516 | const next = value[0]; |
| 517 | if (next === "stacked" || next === "split") { |
| 518 | setDiffRenderMode(next); |
| 519 | } |
| 520 | }} |
| 521 | > |
| 522 | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 523 | <Rows3Icon className="size-3" /> |
| 524 | </Toggle> |
| 525 | <Toggle aria-label="Split diff view" value="split"> |
| 526 | <Columns2Icon className="size-3" /> |
| 527 | </Toggle> |
| 528 | </ToggleGroup> |
| 529 | <Toggle |
| 530 | aria-label={diffWordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"} |
| 531 | title={diffWordWrap ? "Disable line wrapping" : "Enable line wrapping"} |
| 532 | variant="outline" |
| 533 | size="xs" |
| 534 | pressed={diffWordWrap} |
| 535 | onPressedChange={(pressed) => { |
| 536 | setDiffWordWrap(Boolean(pressed)); |
| 537 | }} |
| 538 | > |
| 539 | <TextWrapIcon className="size-3" /> |
| 510 | 540 | </Toggle> |
| 511 | | </ToggleGroup> |
| 541 | </div> |
| 512 | 542 | </> |
| 513 | 543 | ); |
| 514 | 544 | |
| ... | ... | @@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 582 | 612 | options={{ |
| 583 | 613 | diffStyle: diffRenderMode === "split" ? "split" : "unified", |
| 584 | 614 | lineDiffType: "none", |
| 615 | overflow: diffWordWrap ? "wrap" : "scroll", |
| 585 | 616 | theme: resolveDiffThemeName(resolvedTheme), |
| 586 | 617 | themeType: resolvedTheme as DiffThemeType, |
| 587 | 618 | unsafeCSS: DIFF_PANEL_UNSAFE_CSS, |
| ... | ... | @@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 595 | 626 | <div className="h-full overflow-auto p-2"> |
| 596 | 627 | <div className="space-y-2"> |
| 597 | 628 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> |
| 598 | | <pre className="max-h-[72vh] overflow-auto rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90"> |
| 629 | <pre |
| 630 | className={cn( |
| 631 | "max-h-[72vh] rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90", |
| 632 | diffWordWrap |
| 633 | ? "overflow-auto whitespace-pre-wrap wrap-break-word" |
| 634 | : "overflow-auto", |
| 635 | )} |
| 636 | > |
| 599 | 637 | {renderablePatch.text} |
| 600 | 638 | </pre> |
| 601 | 639 | </div> |