Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ export const globalSettingsSchema = z.object({
includeTaskHistoryInEnhance: z.boolean().optional(),
historyPreviewCollapsed: z.boolean().optional(),
reasoningBlockCollapsed: z.boolean().optional(),
/**
* Whether to auto-expand diffs in "Roo wants to edit this file" chat messages.
* @default false
*/
autoExpandDiffs: z.boolean().optional(),
/**
* Controls the keyboard behavior for sending messages in the chat input.
* - "send": Enter sends message, Shift+Enter creates newline (default)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ export type ExtensionState = Pick<
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "autoExpandDiffs"
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2184,6 +2184,7 @@ export class ClineProvider
maxTotalImageSize,
historyPreviewCollapsed,
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
cloudUserInfo,
cloudIsAuthenticated,
Expand Down Expand Up @@ -2310,6 +2311,7 @@ export class ClineProvider
settingsImportedAt: this.settingsImportedAt,
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
autoExpandDiffs: autoExpandDiffs ?? false,
enterBehavior: enterBehavior ?? "send",
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
Expand Down Expand Up @@ -2533,6 +2535,7 @@ export class ClineProvider
maxTotalImageSize: stateValues.maxTotalImageSize ?? 20,
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
autoExpandDiffs: stateValues.autoExpandDiffs ?? false,
enterBehavior: stateValues.enterBehavior ?? "send",
cloudUserInfo,
cloudIsAuthenticated,
Expand Down
41 changes: 40 additions & 1 deletion webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,37 @@ export interface ChatViewRef {

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

/** Tool names that produce file diffs in the chat UI. */
const DIFF_TOOL_NAMES = new Set([
"editedExistingFile",
"appliedDiff",
"newFileCreated",
"searchAndReplace",
"search_and_replace",
"search_replace",
"edit",
"edit_file",
"apply_patch",
"apply_diff",
"insertContent",
])

/**
* Returns true when a message represents a diff-tool invocation that should
* be auto-expanded when the `autoExpandDiffs` setting is enabled.
*/
function isDiffToolMessage(message: ClineMessage): boolean {
if (message.ask !== "tool") {
return false
}
try {
const tool = JSON.parse(message.text || "{}") as ClineSayTool
return DIFF_TOOL_NAMES.has(tool.tool as string)
} catch {
return false
}
}

const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0

const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewProps> = (
Expand Down Expand Up @@ -93,6 +124,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
cloudIsAuthenticated,
messageQueue = [],
showWorktreesInHomeScreen,
autoExpandDiffs,
} = useExtensionState()

// Show a WarningRow when the user sends a message with a retired provider.
Expand Down Expand Up @@ -1403,7 +1435,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
<ChatRow
key={messageOrGroup.ts}
message={messageOrGroup}
isExpanded={expandedRows[messageOrGroup.ts] || false}
isExpanded={
expandedRows[messageOrGroup.ts] !== undefined
? expandedRows[messageOrGroup.ts]
: autoExpandDiffs
? isDiffToolMessage(messageOrGroup)
: false
}
onToggleExpand={toggleRowExpansion} // This was already stabilized
lastModifiedMessage={modifiedMessages.at(-1)} // Original direct access
isLast={index === groupedMessages.length - 1} // Original direct access
Expand Down Expand Up @@ -1447,6 +1485,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
isFollowUpAutoApprovalPaused,
enableButtons,
primaryButtonText,
autoExpandDiffs,
],
)

Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
includeCurrentTime,
includeCurrentCost,
Expand Down Expand Up @@ -412,6 +413,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
followupAutoApproveTimeoutMs,
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
autoExpandDiffs: autoExpandDiffs ?? false,
enterBehavior: enterBehavior ?? "send",
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
Expand Down Expand Up @@ -891,6 +893,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{renderTab === "ui" && (
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
autoExpandDiffs={autoExpandDiffs ?? false}
enterBehavior={enterBehavior ?? "send"}
setCachedStateField={setCachedStateField}
/>
Expand Down
29 changes: 29 additions & 0 deletions webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"

interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
autoExpandDiffs: boolean
enterBehavior: "send" | "newline"
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
setCachedStateField,
...props
Expand All @@ -38,6 +40,15 @@ export const UISettings = ({
})
}

const handleAutoExpandDiffsChange = (value: boolean) => {
setCachedStateField("autoExpandDiffs", value)

// Track telemetry event
telemetryClient.capture("ui_settings_auto_expand_diffs_changed", {
enabled: value,
})
}

const handleEnterBehaviorChange = (requireCtrlEnter: boolean) => {
const newBehavior = requireCtrlEnter ? "newline" : "send"
setCachedStateField("enterBehavior", newBehavior)
Expand Down Expand Up @@ -72,6 +83,24 @@ export const UISettings = ({
</div>
</SearchableSetting>

{/* Auto-Expand Diffs Setting */}
<SearchableSetting
settingId="ui-auto-expand-diffs"
section="ui"
label={t("settings:ui.autoExpandDiffs.label")}>
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={autoExpandDiffs}
onChange={(e: any) => handleAutoExpandDiffsChange(e.target.checked)}
data-testid="auto-expand-diffs-checkbox">
<span className="font-medium">{t("settings:ui.autoExpandDiffs.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
{t("settings:ui.autoExpandDiffs.description")}
</div>
</div>
</SearchableSetting>

{/* Enter Key Behavior Setting */}
<SearchableSetting
settingId="ui-enter-behavior"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ describe("SettingsView - Change Detection Fix", () => {
openRouterImageApiKey: undefined,
openRouterImageGenerationSelectedModel: undefined,
reasoningBlockCollapsed: true,
autoExpandDiffs: false,
...overrides,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ describe("SettingsView - Unsaved Changes Detection", () => {
openRouterImageApiKey: undefined,
openRouterImageGenerationSelectedModel: undefined,
reasoningBlockCollapsed: true,
autoExpandDiffs: false,
}

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { UISettings } from "../UISettings"
describe("UISettings", () => {
const defaultProps = {
reasoningBlockCollapsed: false,
autoExpandDiffs: false,
enterBehavior: "send" as const,
setCachedStateField: vi.fn(),
}
Expand Down Expand Up @@ -41,4 +42,28 @@ describe("UISettings", () => {
rerender(<UISettings {...defaultProps} reasoningBlockCollapsed={true} />)
expect(checkbox.checked).toBe(true)
})

it("renders the auto-expand diffs checkbox", () => {
const { getByTestId } = render(<UISettings {...defaultProps} />)
const checkbox = getByTestId("auto-expand-diffs-checkbox")
expect(checkbox).toBeTruthy()
})

it("displays the correct initial state for auto-expand diffs", () => {
const { getByTestId } = render(<UISettings {...defaultProps} autoExpandDiffs={true} />)
const checkbox = getByTestId("auto-expand-diffs-checkbox") as HTMLInputElement
expect(checkbox.checked).toBe(true)
})

it("calls setCachedStateField when auto-expand diffs checkbox is toggled", async () => {
const setCachedStateField = vi.fn()
const { getByTestId } = render(<UISettings {...defaultProps} setCachedStateField={setCachedStateField} />)

const checkbox = getByTestId("auto-expand-diffs-checkbox")
fireEvent.click(checkbox)

await waitFor(() => {
expect(setCachedStateField).toHaveBeenCalledWith("autoExpandDiffs", true)
})
})
})
5 changes: 5 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export interface ExtensionStateContextType extends ExtensionState {
togglePinnedApiConfig: (configName: string) => void
setHistoryPreviewCollapsed: (value: boolean) => void
setReasoningBlockCollapsed: (value: boolean) => void
autoExpandDiffs?: boolean
setAutoExpandDiffs: (value: boolean) => void
enterBehavior?: "send" | "newline"
setEnterBehavior: (value: "send" | "newline") => void
autoCondenseContext: boolean
Expand Down Expand Up @@ -235,6 +237,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
terminalZdotdir: false, // Default ZDOTDIR handling setting
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
reasoningBlockCollapsed: true, // Default to collapsed
autoExpandDiffs: false, // Default to collapsed (current behavior)
enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline
cloudUserInfo: null,
cloudIsAuthenticated: false,
Expand Down Expand Up @@ -584,6 +587,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })),
setReasoningBlockCollapsed: (value) =>
setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })),
autoExpandDiffs: state.autoExpandDiffs ?? false,
setAutoExpandDiffs: (value) => setState((prevState) => ({ ...prevState, autoExpandDiffs: value })),
enterBehavior: state.enterBehavior ?? "send",
setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: value })),
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),
Expand Down
4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@
"label": "Collapse Thinking messages by default",
"description": "When enabled, thinking blocks will be collapsed by default until you interact with them"
},
"autoExpandDiffs": {
"label": "Auto-expand diffs in edit messages",
"description": "When enabled, code diffs in \"Roo wants to edit this file\" messages will be expanded by default instead of collapsed"
},
"requireCtrlEnterToSend": {
"label": "Require {{primaryMod}}+Enter to send messages",
"description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter"
Expand Down
Loading