Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 98cd31d

Browse files
committed
feat: add setting to auto-expand diffs in edit messages
Adds a new "Auto-expand diffs in edit messages" setting under Settings > UI that, when enabled, automatically expands code diffs in "Roo wants to edit this file" chat messages instead of requiring users to click each one. The setting: - Defaults to false (preserving current collapsed behavior) - Respects user toggles (clicking to collapse overrides auto-expand) - Works with all diff tool types (editedExistingFile, appliedDiff, newFileCreated, searchAndReplace, insertContent, etc.) - Diffs still respect the existing 300px max-height with scrollbar Closes #10955
1 parent cb83656 commit 98cd31d

11 files changed

Lines changed: 117 additions & 1 deletion

File tree

packages/types/src/global-settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ export const globalSettingsSchema = z.object({
201201
includeTaskHistoryInEnhance: z.boolean().optional(),
202202
historyPreviewCollapsed: z.boolean().optional(),
203203
reasoningBlockCollapsed: z.boolean().optional(),
204+
/**
205+
* Whether to auto-expand diffs in "Roo wants to edit this file" chat messages.
206+
* @default false
207+
*/
208+
autoExpandDiffs: z.boolean().optional(),
204209
/**
205210
* Controls the keyboard behavior for sending messages in the chat input.
206211
* - "send": Enter sends message, Shift+Enter creates newline (default)

packages/types/src/vscode-extension-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ export type ExtensionState = Pick<
299299
| "openRouterImageGenerationSelectedModel"
300300
| "includeTaskHistoryInEnhance"
301301
| "reasoningBlockCollapsed"
302+
| "autoExpandDiffs"
302303
| "enterBehavior"
303304
| "includeCurrentTime"
304305
| "includeCurrentCost"

src/core/webview/ClineProvider.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,6 +2184,7 @@ export class ClineProvider
21842184
maxTotalImageSize,
21852185
historyPreviewCollapsed,
21862186
reasoningBlockCollapsed,
2187+
autoExpandDiffs,
21872188
enterBehavior,
21882189
cloudUserInfo,
21892190
cloudIsAuthenticated,
@@ -2310,6 +2311,7 @@ export class ClineProvider
23102311
settingsImportedAt: this.settingsImportedAt,
23112312
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
23122313
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
2314+
autoExpandDiffs: autoExpandDiffs ?? false,
23132315
enterBehavior: enterBehavior ?? "send",
23142316
cloudUserInfo,
23152317
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
@@ -2533,6 +2535,7 @@ export class ClineProvider
25332535
maxTotalImageSize: stateValues.maxTotalImageSize ?? 20,
25342536
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
25352537
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
2538+
autoExpandDiffs: stateValues.autoExpandDiffs ?? false,
25362539
enterBehavior: stateValues.enterBehavior ?? "send",
25372540
cloudUserInfo,
25382541
cloudIsAuthenticated,

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,37 @@ export interface ChatViewRef {
6363

6464
export const MAX_IMAGES_PER_MESSAGE = 20 // This is the Anthropic limit.
6565

66+
/** Tool names that produce file diffs in the chat UI. */
67+
const DIFF_TOOL_NAMES = new Set([
68+
"editedExistingFile",
69+
"appliedDiff",
70+
"newFileCreated",
71+
"searchAndReplace",
72+
"search_and_replace",
73+
"search_replace",
74+
"edit",
75+
"edit_file",
76+
"apply_patch",
77+
"apply_diff",
78+
"insertContent",
79+
])
80+
81+
/**
82+
* Returns true when a message represents a diff-tool invocation that should
83+
* be auto-expanded when the `autoExpandDiffs` setting is enabled.
84+
*/
85+
function isDiffToolMessage(message: ClineMessage): boolean {
86+
if (message.ask !== "tool") {
87+
return false
88+
}
89+
try {
90+
const tool = JSON.parse(message.text || "{}") as ClineSayTool
91+
return DIFF_TOOL_NAMES.has(tool.tool as string)
92+
} catch {
93+
return false
94+
}
95+
}
96+
6697
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
6798

6899
const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewProps> = (
@@ -93,6 +124,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
93124
cloudIsAuthenticated,
94125
messageQueue = [],
95126
showWorktreesInHomeScreen,
127+
autoExpandDiffs,
96128
} = useExtensionState()
97129

98130
// Show a WarningRow when the user sends a message with a retired provider.
@@ -1403,7 +1435,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14031435
<ChatRow
14041436
key={messageOrGroup.ts}
14051437
message={messageOrGroup}
1406-
isExpanded={expandedRows[messageOrGroup.ts] || false}
1438+
isExpanded={
1439+
expandedRows[messageOrGroup.ts] !== undefined
1440+
? expandedRows[messageOrGroup.ts]
1441+
: autoExpandDiffs
1442+
? isDiffToolMessage(messageOrGroup)
1443+
: false
1444+
}
14071445
onToggleExpand={toggleRowExpansion} // This was already stabilized
14081446
lastModifiedMessage={modifiedMessages.at(-1)} // Original direct access
14091447
isLast={index === groupedMessages.length - 1} // Original direct access
@@ -1447,6 +1485,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14471485
isFollowUpAutoApprovalPaused,
14481486
enableButtons,
14491487
primaryButtonText,
1488+
autoExpandDiffs,
14501489
],
14511490
)
14521491

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
199199
openRouterImageApiKey,
200200
openRouterImageGenerationSelectedModel,
201201
reasoningBlockCollapsed,
202+
autoExpandDiffs,
202203
enterBehavior,
203204
includeCurrentTime,
204205
includeCurrentCost,
@@ -412,6 +413,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
412413
followupAutoApproveTimeoutMs,
413414
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
414415
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
416+
autoExpandDiffs: autoExpandDiffs ?? false,
415417
enterBehavior: enterBehavior ?? "send",
416418
includeCurrentTime: includeCurrentTime ?? true,
417419
includeCurrentCost: includeCurrentCost ?? true,
@@ -891,6 +893,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
891893
{renderTab === "ui" && (
892894
<UISettings
893895
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
896+
autoExpandDiffs={autoExpandDiffs ?? false}
894897
enterBehavior={enterBehavior ?? "send"}
895898
setCachedStateField={setCachedStateField}
896899
/>

webview-ui/src/components/settings/UISettings.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"
1111

1212
interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
1313
reasoningBlockCollapsed: boolean
14+
autoExpandDiffs: boolean
1415
enterBehavior: "send" | "newline"
1516
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
1617
}
1718

1819
export const UISettings = ({
1920
reasoningBlockCollapsed,
21+
autoExpandDiffs,
2022
enterBehavior,
2123
setCachedStateField,
2224
...props
@@ -38,6 +40,15 @@ export const UISettings = ({
3840
})
3941
}
4042

43+
const handleAutoExpandDiffsChange = (value: boolean) => {
44+
setCachedStateField("autoExpandDiffs", value)
45+
46+
// Track telemetry event
47+
telemetryClient.capture("ui_settings_auto_expand_diffs_changed", {
48+
enabled: value,
49+
})
50+
}
51+
4152
const handleEnterBehaviorChange = (requireCtrlEnter: boolean) => {
4253
const newBehavior = requireCtrlEnter ? "newline" : "send"
4354
setCachedStateField("enterBehavior", newBehavior)
@@ -72,6 +83,24 @@ export const UISettings = ({
7283
</div>
7384
</SearchableSetting>
7485

86+
{/* Auto-Expand Diffs Setting */}
87+
<SearchableSetting
88+
settingId="ui-auto-expand-diffs"
89+
section="ui"
90+
label={t("settings:ui.autoExpandDiffs.label")}>
91+
<div className="flex flex-col gap-1">
92+
<VSCodeCheckbox
93+
checked={autoExpandDiffs}
94+
onChange={(e: any) => handleAutoExpandDiffsChange(e.target.checked)}
95+
data-testid="auto-expand-diffs-checkbox">
96+
<span className="font-medium">{t("settings:ui.autoExpandDiffs.label")}</span>
97+
</VSCodeCheckbox>
98+
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
99+
{t("settings:ui.autoExpandDiffs.description")}
100+
</div>
101+
</div>
102+
</SearchableSetting>
103+
75104
{/* Enter Key Behavior Setting */}
76105
<SearchableSetting
77106
settingId="ui-enter-behavior"

webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ describe("SettingsView - Change Detection Fix", () => {
302302
openRouterImageApiKey: undefined,
303303
openRouterImageGenerationSelectedModel: undefined,
304304
reasoningBlockCollapsed: true,
305+
autoExpandDiffs: false,
305306
...overrides,
306307
})
307308

webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ describe("SettingsView - Unsaved Changes Detection", () => {
307307
openRouterImageApiKey: undefined,
308308
openRouterImageGenerationSelectedModel: undefined,
309309
reasoningBlockCollapsed: true,
310+
autoExpandDiffs: false,
310311
}
311312

312313
beforeEach(() => {

webview-ui/src/components/settings/__tests__/UISettings.spec.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UISettings } from "../UISettings"
55
describe("UISettings", () => {
66
const defaultProps = {
77
reasoningBlockCollapsed: false,
8+
autoExpandDiffs: false,
89
enterBehavior: "send" as const,
910
setCachedStateField: vi.fn(),
1011
}
@@ -41,4 +42,28 @@ describe("UISettings", () => {
4142
rerender(<UISettings {...defaultProps} reasoningBlockCollapsed={true} />)
4243
expect(checkbox.checked).toBe(true)
4344
})
45+
46+
it("renders the auto-expand diffs checkbox", () => {
47+
const { getByTestId } = render(<UISettings {...defaultProps} />)
48+
const checkbox = getByTestId("auto-expand-diffs-checkbox")
49+
expect(checkbox).toBeTruthy()
50+
})
51+
52+
it("displays the correct initial state for auto-expand diffs", () => {
53+
const { getByTestId } = render(<UISettings {...defaultProps} autoExpandDiffs={true} />)
54+
const checkbox = getByTestId("auto-expand-diffs-checkbox") as HTMLInputElement
55+
expect(checkbox.checked).toBe(true)
56+
})
57+
58+
it("calls setCachedStateField when auto-expand diffs checkbox is toggled", async () => {
59+
const setCachedStateField = vi.fn()
60+
const { getByTestId } = render(<UISettings {...defaultProps} setCachedStateField={setCachedStateField} />)
61+
62+
const checkbox = getByTestId("auto-expand-diffs-checkbox")
63+
fireEvent.click(checkbox)
64+
65+
await waitFor(() => {
66+
expect(setCachedStateField).toHaveBeenCalledWith("autoExpandDiffs", true)
67+
})
68+
})
4469
})

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export interface ExtensionStateContextType extends ExtensionState {
124124
togglePinnedApiConfig: (configName: string) => void
125125
setHistoryPreviewCollapsed: (value: boolean) => void
126126
setReasoningBlockCollapsed: (value: boolean) => void
127+
autoExpandDiffs?: boolean
128+
setAutoExpandDiffs: (value: boolean) => void
127129
enterBehavior?: "send" | "newline"
128130
setEnterBehavior: (value: "send" | "newline") => void
129131
autoCondenseContext: boolean
@@ -235,6 +237,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
235237
terminalZdotdir: false, // Default ZDOTDIR handling setting
236238
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
237239
reasoningBlockCollapsed: true, // Default to collapsed
240+
autoExpandDiffs: false, // Default to collapsed (current behavior)
238241
enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline
239242
cloudUserInfo: null,
240243
cloudIsAuthenticated: false,
@@ -584,6 +587,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
584587
setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })),
585588
setReasoningBlockCollapsed: (value) =>
586589
setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })),
590+
autoExpandDiffs: state.autoExpandDiffs ?? false,
591+
setAutoExpandDiffs: (value) => setState((prevState) => ({ ...prevState, autoExpandDiffs: value })),
587592
enterBehavior: state.enterBehavior ?? "send",
588593
setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: value })),
589594
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),

0 commit comments

Comments
 (0)