diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 99a62f663..cb7b5fd9c 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -56,6 +56,7 @@ export const AppSettingsSchema = Schema.Struct({ codexHomePath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")), defaultThreadEnvMode: EnvMode.pipe(withDefaults(() => "local" as const satisfies EnvMode)), confirmThreadDelete: Schema.Boolean.pipe(withDefaults(() => true)), + diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)), enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)), timestampFormat: TimestampFormat.pipe(withDefaults(() => DEFAULT_TIMESTAMP_FORMAT)), customCodexModels: Schema.Array(Schema.String).pipe(withDefaults(() => [])), diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 34ad78881..96e021987 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac import { useQuery } from "@tanstack/react-query"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { ThreadId, type TurnId } from "@t3tools/contracts"; -import { ChevronLeftIcon, ChevronRightIcon, Columns2Icon, Rows3Icon } from "lucide-react"; +import { + ChevronLeftIcon, + ChevronRightIcon, + Columns2Icon, + Rows3Icon, + TextWrapIcon, +} from "lucide-react"; import { type WheelEvent as ReactWheelEvent, useCallback, @@ -162,8 +168,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const { resolvedTheme } = useTheme(); const { settings } = useAppSettings(); const [diffRenderMode, setDiffRenderMode] = useState("stacked"); + const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); const patchViewportRef = useRef(null); const turnStripRef = useRef(null); + const previousDiffOpenRef = useRef(false); const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); const routeThreadId = useParams({ @@ -171,6 +179,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null), }); const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); + const diffOpen = diffSearch.diff === "1"; const activeThreadId = routeThreadId; const activeThread = useStore((store) => activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined, @@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ); }, [renderablePatch]); + useEffect(() => { + if (diffOpen && !previousDiffOpenRef.current) { + setDiffWordWrap(settings.diffWordWrap); + } + previousDiffOpenRef.current = diffOpen; + }, [diffOpen, settings.diffWordWrap]); + useEffect(() => { if (!selectedFilePath || !patchViewportRef.current) { return; @@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ))} - { - const next = value[0]; - if (next === "stacked" || next === "split") { - setDiffRenderMode(next); - } - }} - > - - - - - +
+ { + const next = value[0]; + if (next === "stacked" || next === "split") { + setDiffRenderMode(next); + } + }} + > + + + + + + + + { + setDiffWordWrap(Boolean(pressed)); + }} + > + - +
); @@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { options={{ diffStyle: diffRenderMode === "split" ? "split" : "unified", lineDiffType: "none", + overflow: diffWordWrap ? "wrap" : "scroll", theme: resolveDiffThemeName(resolvedTheme), themeType: resolvedTheme as DiffThemeType, unsafeCSS: DIFF_PANEL_UNSAFE_CSS, @@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {

{renderablePatch.reason}

-
+                  
                     {renderablePatch.text}
                   
diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 0fbff1cda..05fd640d0 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -252,6 +252,7 @@ function SettingsRouteView() { const changedSettingLabels = [ ...(theme !== "system" ? ["Theme"] : []), ...(settings.timestampFormat !== defaults.timestampFormat ? ["Time format"] : []), + ...(settings.diffWordWrap !== defaults.diffWordWrap ? ["Diff line wrapping"] : []), ...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming ? ["Assistant output"] : []), @@ -500,6 +501,34 @@ function SettingsRouteView() { } /> + + updateSettings({ + diffWordWrap: defaults.diffWordWrap, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + diffWordWrap: Boolean(checked), + }) + } + aria-label="Wrap diff lines by default" + /> + } + /> +