refactor: move experimentalPreventFocusDisruption to experiments system

- Remove experimentalPreventFocusDisruption from VSCode settings
- Add PREVENT_FOCUS_DISRUPTION to experiments system
- Update DiffViewProvider to use experiments instead of VSCode config
- Update Task.ts to pass experiments to DiffViewProvider
- Add localization entries for the new experiment
- Update tests to use the experiments system

This change moves the experimental setting from VSCode configuration to the plugin's experimental settings system as requested.
This commit is contained in:
Roo Code 2025-07-23 07:12:18 +00:00
parent bdcee809b9
commit 99aed8db72
10 changed files with 60 additions and 40 deletions

View file

@ -6,7 +6,7 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
* ExperimentId
*/
export const experimentIds = ["powerSteering", "multiFileApplyDiff"] as const
export const experimentIds = ["powerSteering", "multiFileApplyDiff", "preventFocusDisruption"] as const
export const experimentIdsSchema = z.enum(experimentIds)
@ -19,6 +19,7 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
export const experimentsSchema = z.object({
powerSteering: z.boolean().optional(),
multiFileApplyDiff: z.boolean().optional(),
preventFocusDisruption: z.boolean().optional(),
})
export type Experiments = z.infer<typeof experimentsSchema>

View file

@ -288,6 +288,9 @@ export class Task extends EventEmitter<ClineEvents> {
if (isMultiFileApplyDiffEnabled) {
this.diffStrategy = new MultiFileSearchReplaceDiffStrategy(this.fuzzyMatchThreshold)
}
// Update DiffViewProvider with experiments
this.diffViewProvider.setExperiments(state.experiments ?? {})
})
}

View file

@ -14,6 +14,8 @@ import { ClineSayTool } from "../../shared/ExtensionMessage"
import { Task } from "../../core/task/Task"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { Package } from "../../shared/package"
import { EXPERIMENT_IDS, experiments as Experiments } from "../../shared/experiments"
import type { Experiments as ExperimentsType } from "@roo-code/types"
import { DecorationController } from "./DecorationController"
@ -37,8 +39,18 @@ export class DiffViewProvider {
private activeLineController?: DecorationController
private streamedLines: string[] = []
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
private experiments?: ExperimentsType
constructor(private cwd: string) {}
constructor(
private cwd: string,
experiments?: ExperimentsType,
) {
this.experiments = experiments
}
public setExperiments(experiments: ExperimentsType) {
this.experiments = experiments
}
async open(relPath: string): Promise<void> {
this.relPath = relPath
@ -203,9 +215,9 @@ export class DiffViewProvider {
}
// Check if the experimental setting is enabled
const preventFocusDisruption = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("experimentalPreventFocusDisruption", false)
const preventFocusDisruption = this.experiments
? Experiments.isEnabled(this.experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)
: false
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
@ -401,9 +413,9 @@ export class DiffViewProvider {
if (this.documentWasOpen) {
// Check if the experimental setting is enabled
const preventFocusDisruption = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("experimentalPreventFocusDisruption", false)
const preventFocusDisruption = this.experiments
? Experiments.isEnabled(this.experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)
: false
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
@ -462,9 +474,9 @@ export class DiffViewProvider {
const uri = vscode.Uri.file(path.resolve(this.cwd, this.relPath))
// Check if the experimental setting is enabled
const preventFocusDisruption = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("experimentalPreventFocusDisruption", false)
const preventFocusDisruption = this.experiments
? Experiments.isEnabled(this.experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)
: false
// If this diff editor is already open (ie if a previous write file was
// interrupted) then we should activate that instead of opening a new

View file

@ -2,6 +2,7 @@ import { DiffViewProvider, DIFF_VIEW_URI_SCHEME, DIFF_VIEW_LABEL_CHANGES } from
import * as vscode from "vscode"
import * as path from "path"
import delay from "delay"
import { EXPERIMENT_IDS } from "../../../shared/experiments"
// Mock delay
vi.mock("delay", () => ({
@ -425,10 +426,11 @@ describe("DiffViewProvider", () => {
describe("experimentalPreventFocusDisruption setting", () => {
it("should preserve focus when experimentalPreventFocusDisruption is enabled", async () => {
// Mock the configuration to return true for experimentalPreventFocusDisruption
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn().mockReturnValue(true),
} as any)
// Create a new DiffViewProvider with experiments enabled
const experiments = {
[EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION]: true,
}
diffViewProvider = new DiffViewProvider(mockCwd, experiments)
// Setup mock editor
const mockEditor = {
@ -482,10 +484,11 @@ describe("DiffViewProvider", () => {
})
it("should not preserve focus when experimentalPreventFocusDisruption is disabled", async () => {
// Mock the configuration to return false for experimentalPreventFocusDisruption
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn().mockReturnValue(false),
} as any)
// Create a new DiffViewProvider with experiments disabled
const experiments = {
[EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION]: false,
}
diffViewProvider = new DiffViewProvider(mockCwd, experiments)
// Setup mock editor
const mockEditor = {
@ -539,10 +542,11 @@ describe("DiffViewProvider", () => {
})
it("should preserve focus in saveChanges when experimentalPreventFocusDisruption is enabled", async () => {
// Mock the configuration to return true
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn().mockReturnValue(true),
} as any)
// Create a new DiffViewProvider with experiments enabled
const experiments = {
[EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION]: true,
}
diffViewProvider = new DiffViewProvider(mockCwd, experiments)
// Setup for saveChanges
;(diffViewProvider as any).relPath = "test.ts"
@ -571,10 +575,11 @@ describe("DiffViewProvider", () => {
})
it("should preserve focus in revertChanges when experimentalPreventFocusDisruption is enabled", async () => {
// Mock the configuration to return true
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn().mockReturnValue(true),
} as any)
// Create a new DiffViewProvider with experiments enabled
const experiments = {
[EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION]: true,
}
diffViewProvider = new DiffViewProvider(mockCwd, experiments)
// Setup for revertChanges
;(diffViewProvider as any).relPath = "test.ts"

View file

@ -386,11 +386,6 @@
"type": "string",
"default": "",
"description": "%settings.autoImportSettingsPath.description%"
},
"roo-cline.experimentalPreventFocusDisruption": {
"type": "boolean",
"default": false,
"description": "%settings.experimentalPreventFocusDisruption.description%"
}
}
}

View file

@ -36,6 +36,5 @@
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
"settings.experimentalPreventFocusDisruption.description": "(Experimental) Prevent file edits from stealing focus. When enabled, diff views and file edits will not disrupt your current work. Files will update in the background without forcing you to switch context."
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import."
}

View file

@ -28,6 +28,7 @@ describe("experiments", () => {
const experiments: Record<ExperimentId, boolean> = {
powerSteering: false,
multiFileApplyDiff: false,
preventFocusDisruption: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
})
@ -36,6 +37,7 @@ describe("experiments", () => {
const experiments: Record<ExperimentId, boolean> = {
powerSteering: true,
multiFileApplyDiff: false,
preventFocusDisruption: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true)
})
@ -44,6 +46,7 @@ describe("experiments", () => {
const experiments: Record<ExperimentId, boolean> = {
powerSteering: false,
multiFileApplyDiff: false,
preventFocusDisruption: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
})

View file

@ -3,6 +3,7 @@ import type { AssertEqual, Equals, Keys, Values, ExperimentId, Experiments } fro
export const EXPERIMENT_IDS = {
MULTI_FILE_APPLY_DIFF: "multiFileApplyDiff",
POWER_STEERING: "powerSteering",
PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption",
} as const satisfies Record<string, ExperimentId>
type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPERIMENT_IDS>>>
@ -16,6 +17,7 @@ interface ExperimentConfig {
export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
MULTI_FILE_APPLY_DIFF: { enabled: false },
POWER_STEERING: { enabled: false },
PREVENT_FOCUS_DISRUPTION: { enabled: false },
}
export const experimentDefault = Object.fromEntries(

View file

@ -222,10 +222,8 @@ describe("mergeExtensionState", () => {
apiConfiguration: { modelMaxThinkingTokens: 456, modelTemperature: 0.3 },
experiments: {
powerSteering: true,
marketplace: false,
disableCompletionCommand: false,
concurrentFileReads: true,
multiFileApplyDiff: true,
preventFocusDisruption: false,
} as Record<ExperimentId, boolean>,
}
@ -238,10 +236,8 @@ describe("mergeExtensionState", () => {
expect(result.experiments).toEqual({
powerSteering: true,
marketplace: false,
disableCompletionCommand: false,
concurrentFileReads: true,
multiFileApplyDiff: true,
preventFocusDisruption: false,
})
})
})

View file

@ -618,6 +618,10 @@
"MULTI_FILE_APPLY_DIFF": {
"name": "Enable concurrent file edits",
"description": "When enabled, Roo can edit multiple files in a single request. When disabled, Roo must edit files one at a time. Disabling this can help when working with less capable models or when you want more control over file modifications."
},
"PREVENT_FOCUS_DISRUPTION": {
"name": "Prevent focus disruption on file edits",
"description": "When enabled, file edits will not automatically switch focus to the edited file. This helps maintain your current workflow when Roo makes changes to multiple files."
}
},
"promptCaching": {