Skip to content

Commit 7bfd460

Browse files
raphaelluethyjuliusmarminge
authored andcommitted
feat: add word wrapping setting and in panel button
1 parent be1abc8 commit 7bfd460

File tree

3 files changed

+88
-20
lines changed

3 files changed

+88
-20
lines changed

apps/web/src/appSettings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const AppSettingsSchema = Schema.Struct({
5656
codexHomePath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")),
5757
defaultThreadEnvMode: EnvMode.pipe(withDefaults(() => "local" as const satisfies EnvMode)),
5858
confirmThreadDelete: Schema.Boolean.pipe(withDefaults(() => true)),
59+
diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)),
5960
enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)),
6061
timestampFormat: TimestampFormat.pipe(withDefaults(() => DEFAULT_TIMESTAMP_FORMAT)),
6162
customCodexModels: Schema.Array(Schema.String).pipe(withDefaults(() => [])),

apps/web/src/components/DiffPanel.tsx

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac
33
import { useQuery } from "@tanstack/react-query";
44
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
55
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";
713
import {
814
type WheelEvent as ReactWheelEvent,
915
useCallback,
@@ -162,15 +168,18 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
162168
const { resolvedTheme } = useTheme();
163169
const { settings } = useAppSettings();
164170
const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked");
171+
const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap);
165172
const patchViewportRef = useRef<HTMLDivElement>(null);
166173
const turnStripRef = useRef<HTMLDivElement>(null);
174+
const previousDiffOpenRef = useRef(false);
167175
const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false);
168176
const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false);
169177
const routeThreadId = useParams({
170178
strict: false,
171179
select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null),
172180
});
173181
const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });
182+
const diffOpen = diffSearch.diff === "1";
174183
const activeThreadId = routeThreadId;
175184
const activeThread = useStore((store) =>
176185
activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined,
@@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
293302
);
294303
}, [renderablePatch]);
295304

305+
useEffect(() => {
306+
if (diffOpen && !previousDiffOpenRef.current) {
307+
setDiffWordWrap(settings.diffWordWrap);
308+
}
309+
previousDiffOpenRef.current = diffOpen;
310+
}, [diffOpen, settings.diffWordWrap]);
311+
296312
useEffect(() => {
297313
if (!selectedFilePath || !patchViewportRef.current) {
298314
return;
@@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
490506
))}
491507
</div>
492508
</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" />
510540
</Toggle>
511-
</ToggleGroup>
541+
</div>
512542
</>
513543
);
514544

@@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
582612
options={{
583613
diffStyle: diffRenderMode === "split" ? "split" : "unified",
584614
lineDiffType: "none",
615+
overflow: diffWordWrap ? "wrap" : "scroll",
585616
theme: resolveDiffThemeName(resolvedTheme),
586617
themeType: resolvedTheme as DiffThemeType,
587618
unsafeCSS: DIFF_PANEL_UNSAFE_CSS,
@@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
595626
<div className="h-full overflow-auto p-2">
596627
<div className="space-y-2">
597628
<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+
>
599637
{renderablePatch.text}
600638
</pre>
601639
</div>

apps/web/src/routes/_chat.settings.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ function SettingsRouteView() {
252252
const changedSettingLabels = [
253253
...(theme !== "system" ? ["Theme"] : []),
254254
...(settings.timestampFormat !== defaults.timestampFormat ? ["Time format"] : []),
255+
...(settings.diffWordWrap !== defaults.diffWordWrap ? ["Diff line wrapping"] : []),
255256
...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming
256257
? ["Assistant output"]
257258
: []),
@@ -500,6 +501,34 @@ function SettingsRouteView() {
500501
}
501502
/>
502503

504+
<SettingsRow
505+
title="Diff line wrapping"
506+
description="Set the default wrap state when the diff panel opens. The in-panel wrap toggle only affects the current diff session."
507+
resetAction={
508+
settings.diffWordWrap !== defaults.diffWordWrap ? (
509+
<SettingResetButton
510+
label="diff line wrapping"
511+
onClick={() =>
512+
updateSettings({
513+
diffWordWrap: defaults.diffWordWrap,
514+
})
515+
}
516+
/>
517+
) : null
518+
}
519+
control={
520+
<Switch
521+
checked={settings.diffWordWrap}
522+
onCheckedChange={(checked) =>
523+
updateSettings({
524+
diffWordWrap: Boolean(checked),
525+
})
526+
}
527+
aria-label="Wrap diff lines by default"
528+
/>
529+
}
530+
/>
531+
503532
<SettingsRow
504533
title="Assistant output"
505534
description="Show token-by-token output while a response is in progress."

0 commit comments

Comments
 (0)