Skip to content
Merged
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
14 changes: 14 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ import { languagesSchema } from "./vscode.js"
*/
export const DEFAULT_WRITE_DELAY_MS = 1000

/**
* Default values for the "auto-close files Zoo opened" settings.
*
* These are defined once here and consumed by every site that reads the setting
* (DiffViewProvider save/revert, ClineProvider state serialization, and the
* UISettings checkboxes) so there is a single source of truth for the default
* behavior. Auto-closing edited tabs is opt-in: by default, files Zoo edits stay
* open in the editor (the long-standing behavior). Users who want to save
* context tokens by closing the edited tab after each edit can enable it.
*/
export const DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES = false
export const DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED = false
export const DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES = false

/**
* Default fuzzy matching threshold for the multi-search-replace diff strategy.
* A value of 1.0 (exact match) is used by default for safety, especially when
Expand Down
10 changes: 7 additions & 3 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import {
openRouterDefaultModelId,
DEFAULT_WRITE_DELAY_MS,
DEFAULT_DIFF_FUZZY_THRESHOLD,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
ORGANIZATION_ALLOW_ALL,
DEFAULT_MODES,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
Expand Down Expand Up @@ -2471,9 +2474,10 @@ export class ClineProvider
imageGenerationProvider,
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
autoCloseZooOpenedFiles: autoCloseZooOpenedFiles ?? true,
autoCloseZooOpenedFilesAfterUserEdited: autoCloseZooOpenedFilesAfterUserEdited ?? false,
autoCloseZooOpenedNewFiles: autoCloseZooOpenedNewFiles ?? false,
autoCloseZooOpenedFiles: autoCloseZooOpenedFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
autoCloseZooOpenedFilesAfterUserEdited:
autoCloseZooOpenedFilesAfterUserEdited ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
autoCloseZooOpenedNewFiles: autoCloseZooOpenedNewFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
openAiCodexIsAuthenticated: await (async () => {
try {
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
Expand Down
6 changes: 3 additions & 3 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ describe("ClineProvider", () => {
expect(state.autoCloseZooOpenedNewFiles).toBe(true)
})

it("getStateToPostToWebview defaults autoCloseZooOpenedFiles to true when unset", async () => {
it("getStateToPostToWebview defaults autoCloseZooOpenedFiles to false when unset", async () => {
await provider.resolveWebviewView(mockWebviewView)

// Ensure the settings are not set.
Expand All @@ -1082,8 +1082,8 @@ describe("ClineProvider", () => {

const state = await provider.getStateToPostToWebview()

// Unset values should default to their documented defaults.
expect(state.autoCloseZooOpenedFiles).toBe(true)
// Unset values should default to their documented defaults (opt-in).
expect(state.autoCloseZooOpenedFiles).toBe(false)
expect(state.autoCloseZooOpenedFilesAfterUserEdited).toBe(false)
expect(state.autoCloseZooOpenedNewFiles).toBe(false)
})
Expand Down
36 changes: 24 additions & 12 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import * as diff from "diff"
import stripBom from "strip-bom"
import delay from "delay"

import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import {
type ClineSayTool,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
DEFAULT_WRITE_DELAY_MS,
} from "@roo-code/types"

import { createDirectoriesForFile } from "../../utils/fs"
import { arePathsEqual, getReadablePath } from "../../utils/path"
Expand Down Expand Up @@ -352,9 +358,9 @@ export class DiffViewProvider {
await this.keepOrCloseEditedFile(
absolutePath,
this.userTouchedDiffEditor,
saveState?.autoCloseZooOpenedFiles ?? true,
saveState?.autoCloseZooOpenedFilesAfterUserEdited ?? false,
saveState?.autoCloseZooOpenedNewFiles ?? false,
saveState?.autoCloseZooOpenedFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
saveState?.autoCloseZooOpenedFilesAfterUserEdited ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
saveState?.autoCloseZooOpenedNewFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
)

// Restore any preview tabs the diff evicted, reconstructing the user's
Expand Down Expand Up @@ -563,9 +569,10 @@ export class DiffViewProvider {
await this.keepOrCloseEditedFile(
absolutePath,
false,
revertState?.autoCloseZooOpenedFiles ?? true,
revertState?.autoCloseZooOpenedFilesAfterUserEdited ?? false,
revertState?.autoCloseZooOpenedNewFiles ?? false,
revertState?.autoCloseZooOpenedFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
revertState?.autoCloseZooOpenedFilesAfterUserEdited ??
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
revertState?.autoCloseZooOpenedNewFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
)
}

Expand Down Expand Up @@ -647,16 +654,20 @@ export class DiffViewProvider {
// refinement of the base auto-close, so it has no effect when the base
// setting is off.
// 4. autoCloseZooOpenedFiles=false -> keep the transiently-opened tab.
// 5. Default -> close the transiently-opened tab (current behavior preserved).
// 5. autoCloseZooOpenedFiles=true -> close the transiently-opened tab.
//
// The default value of autoCloseZooOpenedFiles is false (opt-in), so by default
// branch 4 applies and the edited file stays open. See DEFAULT_AUTO_CLOSE_* in
// @roo-code/types for the single source of truth for these defaults.
//
// keepIfTouchedDiff is passed as true from saveChanges() when the user clicked
// or typed inside the diff editor itself.
private async keepOrCloseEditedFile(
absolutePath: string,
keepIfTouchedDiff = false,
autoCloseZooOpenedFiles = true,
autoCloseZooOpenedFilesAfterUserEdited = false,
autoCloseZooOpenedNewFiles = false,
autoCloseZooOpenedFiles = DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
autoCloseZooOpenedFilesAfterUserEdited = DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
autoCloseZooOpenedNewFiles = DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
): Promise<void> {
// Files the user already had open are never auto-closed.
if (this.documentWasOpen) {
Expand All @@ -682,7 +693,8 @@ export class DiffViewProvider {
return
}

// Transient tab opened by Zoo: close by default, keep only when opted out.
// Transient tab opened by Zoo: close only when auto-close is enabled (opt-in);
// keep and re-show it otherwise (the default).
if (autoCloseZooOpenedFiles) {
await this.closeFileTab(absolutePath)
} else {
Expand Down
40 changes: 35 additions & 5 deletions src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ describe("DiffViewProvider", () => {
getState: vi.fn().mockResolvedValue({
includeDiagnosticMessages: true,
maxDiagnosticMessages: 50,
// Auto-closing edited tabs is opt-in by default; the legacy
// "close/keep behavior" suite below asserts the close path, so
// enable it here. The opt-in default itself is covered by the
// dedicated "auto-close settings decision table" suite.
autoCloseZooOpenedFiles: true,
}),
}),
},
Expand Down Expand Up @@ -1711,7 +1716,24 @@ describe("DiffViewProvider", () => {
expect(vscode.window.showTextDocument).toHaveBeenCalled()
})

it("transient tab is closed when autoCloseZooOpenedFiles is true (default)", async () => {
it("transient tab is kept by default when autoCloseZooOpenedFiles is unset (opt-in)", async () => {
// Empty state -> autoCloseZooOpenedFiles is undefined and falls back to the
// centralized default (false), so an untouched transient tab is kept.
const provider = setupProvider({})
const closeFileTab = vi.fn().mockResolvedValue(undefined)
;(provider as any).closeFileTab = closeFileTab
;(provider as any).documentWasOpen = false
;(provider as any).userTouchedDocument = false
;(provider as any).userTouchedDiffEditor = false
vi.mocked(vscode.window.showTextDocument).mockResolvedValue({ revealRange: vi.fn() } as any)

await provider.saveChanges(false)

expect(closeFileTab).not.toHaveBeenCalled()
expect(vscode.window.showTextDocument).toHaveBeenCalled()
})

it("transient tab is closed when autoCloseZooOpenedFiles is true", async () => {
const provider = setupProvider({ autoCloseZooOpenedFiles: true })
const closeFileTab = vi.fn().mockResolvedValue(undefined)
;(provider as any).closeFileTab = closeFileTab
Expand Down Expand Up @@ -1739,7 +1761,12 @@ describe("DiffViewProvider", () => {
})

it("touched tab is closed when autoCloseZooOpenedFilesAfterUserEdited is true", async () => {
const provider = setupProvider({ autoCloseZooOpenedFilesAfterUserEdited: true })
// The after-edit override only closes when the base auto-close is also
// enabled, so set both (the base default is now opt-in/false).
const provider = setupProvider({
autoCloseZooOpenedFiles: true,
autoCloseZooOpenedFilesAfterUserEdited: true,
})
const closeFileTab = vi.fn().mockResolvedValue(undefined)
;(provider as any).closeFileTab = closeFileTab
;(provider as any).documentWasOpen = false
Expand Down Expand Up @@ -1807,18 +1834,21 @@ describe("DiffViewProvider", () => {
expect(vscode.window.showTextDocument).toHaveBeenCalled()
})

it("defaults preserve existing behavior when all settings are unset", async () => {
// No auto-close settings in state: transient tab should be closed (existing default).
it("defaults keep the transient tab open when all settings are unset", async () => {
// No auto-close settings in state: auto-closing is opt-in, so an
// untouched transient tab is kept and re-shown (long-standing behavior).
const provider = setupProvider({})
const closeFileTab = vi.fn().mockResolvedValue(undefined)
;(provider as any).closeFileTab = closeFileTab
;(provider as any).documentWasOpen = false
;(provider as any).userTouchedDocument = false
;(provider as any).userTouchedDiffEditor = false
vi.mocked(vscode.window.showTextDocument).mockResolvedValue({ revealRange: vi.fn() } as any)

await provider.saveChanges(false)

expect(closeFileTab).toHaveBeenCalledWith(mockTargetPath)
expect(closeFileTab).not.toHaveBeenCalled()
expect(vscode.window.showTextDocument).toHaveBeenCalled()
})
})
})
10 changes: 7 additions & 3 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import {
type ProviderSettings,
type ExperimentId,
type TelemetrySetting,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
ImageGenerationProvider,
} from "@roo-code/types"
Expand Down Expand Up @@ -425,9 +428,10 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
maxGitStatusFiles: maxGitStatusFiles ?? 0,
autoCloseZooOpenedFiles: autoCloseZooOpenedFiles ?? true,
autoCloseZooOpenedFilesAfterUserEdited: autoCloseZooOpenedFilesAfterUserEdited ?? false,
autoCloseZooOpenedNewFiles: autoCloseZooOpenedNewFiles ?? false,
autoCloseZooOpenedFiles: autoCloseZooOpenedFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
autoCloseZooOpenedFilesAfterUserEdited:
autoCloseZooOpenedFilesAfterUserEdited ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
autoCloseZooOpenedNewFiles: autoCloseZooOpenedNewFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
profileThresholds,
imageGenerationProvider,
openRouterImageApiKey,
Expand Down
14 changes: 11 additions & 3 deletions webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { HTMLAttributes, useMemo } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { telemetryClient } from "@/utils/TelemetryClient"
import {
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED,
DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES,
} from "@roo-code/types"

import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
Expand Down Expand Up @@ -160,7 +165,7 @@ export const UISettings = ({
label={t("settings:ui.autoCloseZooOpenedFiles.label")}>
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={autoCloseZooOpenedFiles ?? true}
checked={autoCloseZooOpenedFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES}
onChange={(e: any) => setCachedStateField("autoCloseZooOpenedFiles", e.target.checked)}
data-testid="auto-close-zoo-opened-files-checkbox">
<span className="font-medium">{t("settings:ui.autoCloseZooOpenedFiles.label")}</span>
Expand All @@ -178,7 +183,10 @@ export const UISettings = ({
label={t("settings:ui.autoCloseZooOpenedFilesAfterUserEdited.label")}>
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={autoCloseZooOpenedFilesAfterUserEdited ?? false}
checked={
autoCloseZooOpenedFilesAfterUserEdited ??
DEFAULT_AUTO_CLOSE_ZOO_OPENED_FILES_AFTER_USER_EDITED
}
onChange={(e: any) =>
setCachedStateField("autoCloseZooOpenedFilesAfterUserEdited", e.target.checked)
}
Expand All @@ -200,7 +208,7 @@ export const UISettings = ({
label={t("settings:ui.autoCloseZooOpenedNewFiles.label")}>
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={autoCloseZooOpenedNewFiles ?? false}
checked={autoCloseZooOpenedNewFiles ?? DEFAULT_AUTO_CLOSE_ZOO_OPENED_NEW_FILES}
onChange={(e: any) =>
setCachedStateField("autoCloseZooOpenedNewFiles", e.target.checked)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ describe("UISettings", () => {
expect(checkbox.checked).toBe(false)
})

it("autoCloseZooOpenedFiles checkbox defaults to unchecked when prop is unset", () => {
// Omitting the prop simulates the opt-in default (false). A regression that
// flips the fallback back to `?? true` would make this checkbox checked.
const { getByTestId } = render(<UISettings {...defaultProps} />)
const checkbox = getByTestId("auto-close-zoo-opened-files-checkbox") as HTMLInputElement
expect(checkbox.checked).toBe(false)
})

it("calls setCachedStateField with autoCloseZooOpenedFiles when toggled", async () => {
const setCachedStateField = vi.fn()
const { getByTestId } = render(
Expand Down
Loading