Skip to content

feat: add experimental setting to prevent editor focus disruption #6093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
3 changes: 2 additions & 1 deletion packages/types/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? {})
})
}

Expand Down
62 changes: 51 additions & 11 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
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"

Expand All @@ -36,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
Expand Down Expand Up @@ -181,7 +194,10 @@ export class DiffViewProvider {
}
}

async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
async saveChanges(
diagnosticsEnabled: boolean = true,
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
Expand All @@ -198,7 +214,15 @@ export class DiffViewProvider {
await updatedDocument.save()
}

await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })
// Check if the experimental setting is enabled
const preventFocusDisruption = this.experiments
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of the experimental flag: using Experiments.isEnabled to set preserveFocus ensures that file edits won’t steal focus when the experiment is enabled. Consider extracting this flag lookup into a helper function if similar checks occur elsewhere to reduce duplication.

? Experiments.isEnabled(this.experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)
: false

await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
preserveFocus: preventFocusDisruption,
})
await this.closeAllDiffViews()

// Getting diagnostics before and after the file edit is a better approach than
Expand All @@ -216,22 +240,22 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.

let newProblemsMessage = ""

if (diagnosticsEnabled) {
// Add configurable delay to allow linters time to process and clean up issues
// like unused imports (especially important for Go and other languages)
// Ensure delay is non-negative
const safeDelayMs = Math.max(0, writeDelayMs)

try {
await delay(safeDelayMs)
} catch (error) {
// Log error but continue - delay failure shouldn't break the save operation
console.warn(`Failed to apply write delay: ${error}`)
}

const postDiagnostics = vscode.languages.getDiagnostics()

const newProblems = await diagnosticsToProblemsString(
Expand Down Expand Up @@ -388,9 +412,14 @@ export class DiffViewProvider {
await updatedDocument.save()

if (this.documentWasOpen) {
// Check if the experimental setting is enabled
const preventFocusDisruption = this.experiments
? Experiments.isEnabled(this.experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)
: false

await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
preserveFocus: true,
preserveFocus: preventFocusDisruption,
})
}

Expand Down Expand Up @@ -444,6 +473,11 @@ export class DiffViewProvider {

const uri = vscode.Uri.file(path.resolve(this.cwd, this.relPath))

// Check if the experimental setting is enabled
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
// diff.
Expand All @@ -457,7 +491,9 @@ export class DiffViewProvider {
)

if (diffTab && diffTab.input instanceof vscode.TabInputTextDiff) {
const editor = await vscode.window.showTextDocument(diffTab.input.modified, { preserveFocus: true })
const editor = await vscode.window.showTextDocument(diffTab.input.modified, {
preserveFocus: preventFocusDisruption,
})
return editor
}

Expand Down Expand Up @@ -523,7 +559,11 @@ export class DiffViewProvider {
// Pre-open the file as a text document to ensure it doesn't open in preview mode
// This fixes issues with files that have custom editor associations (like markdown preview)
vscode.window
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
.showTextDocument(uri, {
preview: false,
viewColumn: vscode.ViewColumn.Active,
preserveFocus: preventFocusDisruption,
})
.then(() => {
// Execute the diff command after ensuring the file is open as text
return vscode.commands.executeCommand(
Expand All @@ -533,7 +573,7 @@ export class DiffViewProvider {
}),
uri,
`${fileName}: ${fileExists ? `${DIFF_VIEW_LABEL_CHANGES}` : "New File"} (Editable)`,
{ preserveFocus: true },
{ preserveFocus: preventFocusDisruption },
)
})
.then(
Expand Down
Loading