mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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
This commit is contained in:
parent
cb83656718
commit
98cd31d007
11 changed files with 117 additions and 1 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ export type ExtensionState = Pick<
|
|||
| "openRouterImageGenerationSelectedModel"
|
||||
| "includeTaskHistoryInEnhance"
|
||||
| "reasoningBlockCollapsed"
|
||||
| "autoExpandDiffs"
|
||||
| "enterBehavior"
|
||||
| "includeCurrentTime"
|
||||
| "includeCurrentCost"
|
||||
|
|
|
|||
|
|
@ -2184,6 +2184,7 @@ export class ClineProvider
|
|||
maxTotalImageSize,
|
||||
historyPreviewCollapsed,
|
||||
reasoningBlockCollapsed,
|
||||
autoExpandDiffs,
|
||||
enterBehavior,
|
||||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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> = (
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -1447,6 +1485,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
isFollowUpAutoApprovalPaused,
|
||||
enableButtons,
|
||||
primaryButtonText,
|
||||
autoExpandDiffs,
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
openRouterImageApiKey,
|
||||
openRouterImageGenerationSelectedModel,
|
||||
reasoningBlockCollapsed,
|
||||
autoExpandDiffs,
|
||||
enterBehavior,
|
||||
includeCurrentTime,
|
||||
includeCurrentCost,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ describe("SettingsView - Change Detection Fix", () => {
|
|||
openRouterImageApiKey: undefined,
|
||||
openRouterImageGenerationSelectedModel: undefined,
|
||||
reasoningBlockCollapsed: true,
|
||||
autoExpandDiffs: false,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ describe("SettingsView - Unsaved Changes Detection", () => {
|
|||
openRouterImageApiKey: undefined,
|
||||
openRouterImageGenerationSelectedModel: undefined,
|
||||
reasoningBlockCollapsed: true,
|
||||
autoExpandDiffs: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { UISettings } from "../UISettings"
|
|||
describe("UISettings", () => {
|
||||
const defaultProps = {
|
||||
reasoningBlockCollapsed: false,
|
||||
autoExpandDiffs: false,
|
||||
enterBehavior: "send" as const,
|
||||
setCachedStateField: vi.fn(),
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
@ -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 })),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue