| ... | @@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac | ... | @@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac |
| 3 | import { useQuery } from "@tanstack/react-query"; | 3 | import { useQuery } from "@tanstack/react-query"; |
| 4 | import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; | 4 | import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; |
| 5 | import { ThreadId, type TurnId } from "@t3tools/contracts"; | 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 | import { | 13 | import { |
| 8 | type WheelEvent as ReactWheelEvent, | 14 | type WheelEvent as ReactWheelEvent, |
| 9 | useCallback, | 15 | useCallback, |
| ... | @@ -162,8 +168,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -162,8 +168,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 162 | const { resolvedTheme } = useTheme(); | 168 | const { resolvedTheme } = useTheme(); |
| 163 | const { settings } = useAppSettings(); | 169 | const { settings } = useAppSettings(); |
| 164 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); | 170 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| | 171 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 165 | const patchViewportRef = useRef<HTMLDivElement>(null); | 172 | const patchViewportRef = useRef<HTMLDivElement>(null); |
| 166 | const turnStripRef = useRef<HTMLDivElement>(null); | 173 | const turnStripRef = useRef<HTMLDivElement>(null); |
| | 174 | const previousDiffOpenRef = useRef(false); |
| 167 | const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); | 175 | const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); |
| 168 | const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); | 176 | const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); |
| 169 | const routeThreadId = useParams({ | 177 | const routeThreadId = useParams({ |
| ... | @@ -171,6 +179,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -171,6 +179,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 171 | select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null), | 179 | select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null), |
| 172 | }); | 180 | }); |
| 173 | const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); | 181 | const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); |
| | 182 | const diffOpen = diffSearch.diff === "1"; |
| 174 | const activeThreadId = routeThreadId; | 183 | const activeThreadId = routeThreadId; |
| 175 | const activeThread = useStore((store) => | 184 | const activeThread = useStore((store) => |
| 176 | activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined, | 185 | activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined, |
| ... | @@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 293 | ); | 302 | ); |
| 294 | }, [renderablePatch]); | 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 | useEffect(() => { | 312 | useEffect(() => { |
| 297 | if (!selectedFilePath || !patchViewportRef.current) { | 313 | if (!selectedFilePath || !patchViewportRef.current) { |
| 298 | return; | 314 | return; |
| ... | @@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 490 | ))} | 506 | ))} |
| 491 | </div> | 507 | </div> |
| 492 | </div> | 508 | </div> |
| 493 | <ToggleGroup | 509 | <div className="flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]"> |
| 494 | className="shrink-0 [-webkit-app-region:no-drag]" | 510 | <ToggleGroup |
| 495 | variant="outline" | 511 | className="shrink-0" |
| 496 | size="xs" | 512 | variant="outline" |
| 497 | value={[diffRenderMode]} | 513 | size="xs" |
| 498 | onValueChange={(value) => { | 514 | value={[diffRenderMode]} |
| 499 | const next = value[0]; | 515 | onValueChange={(value) => { |
| 500 | if (next === "stacked" || next === "split") { | 516 | const next = value[0]; |
| 501 | setDiffRenderMode(next); | 517 | if (next === "stacked" || next === "split") { |
| 502 | } | 518 | setDiffRenderMode(next); |
| 503 | }} | 519 | } |
| 504 | > | 520 | }} |
| 505 | <Toggle aria-label="Stacked diff view" value="stacked"> | 521 | > |
| 506 | <Rows3Icon className="size-3" /> | 522 | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 507 | </Toggle> | 523 | <Rows3Icon className="size-3" /> |
| 508 | <Toggle aria-label="Split diff view" value="split"> | 524 | </Toggle> |
| 509 | <Columns2Icon className="size-3" /> | 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 | </Toggle> | 540 | </Toggle> |
| 511 | </ToggleGroup> | 541 | </div> |
| 512 | </> | 542 | </> |
| 513 | ); | 543 | ); |
| 514 | | 544 | |
| ... | @@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 582 | options={{ | 612 | options={{ |
| 583 | diffStyle: diffRenderMode === "split" ? "split" : "unified", | 613 | diffStyle: diffRenderMode === "split" ? "split" : "unified", |
| 584 | lineDiffType: "none", | 614 | lineDiffType: "none", |
| | 615 | overflow: diffWordWrap ? "wrap" : "scroll", |
| 585 | theme: resolveDiffThemeName(resolvedTheme), | 616 | theme: resolveDiffThemeName(resolvedTheme), |
| 586 | themeType: resolvedTheme as DiffThemeType, | 617 | themeType: resolvedTheme as DiffThemeType, |
| 587 | unsafeCSS: DIFF_PANEL_UNSAFE_CSS, | 618 | unsafeCSS: DIFF_PANEL_UNSAFE_CSS, |
| ... | @@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 595 | <div className="h-full overflow-auto p-2"> | 626 | <div className="h-full overflow-auto p-2"> |
| 596 | <div className="space-y-2"> | 627 | <div className="space-y-2"> |
| 597 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> | 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 | {renderablePatch.text} | 637 | {renderablePatch.text} |
| 600 | </pre> | 638 | </pre> |
| 601 | </div> | 639 | </div> |