diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 10384db8ed..5424121d67 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -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 export const experimentsSchema = z.object({ powerSteering: z.boolean().optional(), multiFileApplyDiff: z.boolean().optional(), + preventFocusDisruption: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index f046ba67d2..903e3c846e 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -12,6 +12,7 @@ import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { unescapeHtmlEntities } from "../../utils/text-normalization" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" export async function applyDiffToolLegacy( cline: Task, @@ -87,7 +88,7 @@ export async function applyDiffToolLegacy( return } - let originalContent: string | null = await fs.readFile(absolutePath, "utf-8") + const originalContent: string = await fs.readFile(absolutePath, "utf-8") // Apply the diff to the original content const diffResult = (await cline.diffStrategy?.applyDiff( @@ -99,9 +100,6 @@ export async function applyDiffToolLegacy( error: "No diff strategy available", } - // Release the original content from memory as it's no longer needed - originalContent = null - if (!diffResult.success) { cline.consecutiveMistakeCount++ const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 @@ -142,40 +140,79 @@ export async function applyDiffToolLegacy( cline.consecutiveMistakeCount = 0 cline.consecutiveMistakeCountForApplyDiff.delete(relPath) - // Show diff view before asking for approval - cline.diffViewProvider.editType = "modify" - await cline.diffViewProvider.open(relPath) - await cline.diffViewProvider.update(diffResult.content, true) - cline.diffViewProvider.scrollToFirstDiff() + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const diagnosticsEnabled = state?.diagnosticsEnabled ?? true + const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, + ) // Check if file is write-protected const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diffContent, - isProtected: isWriteProtected, - } satisfies ClineSayTool) + if (isPreventFocusDisruptionEnabled) { + // Direct file write without diff view + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diffContent, + isProtected: isWriteProtected, + } satisfies ClineSayTool) - let toolProgressStatus + let toolProgressStatus - if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { - toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) - } + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) + } - const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected) + const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected) - if (!didApprove) { - await cline.diffViewProvider.revertChanges() // Cline likely handles closing the diff view - return - } + if (!didApprove) { + return + } - // Call saveChanges to update the DiffViewProvider properties - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + // Save directly without showing diff view or opening the file + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = originalContent + await cline.diffViewProvider.saveDirectly( + relPath, + diffResult.content, + false, + diagnosticsEnabled, + writeDelayMs, + ) + } else { + // Original behavior with diff view + // Show diff view before asking for approval + cline.diffViewProvider.editType = "modify" + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(diffResult.content, true) + cline.diffViewProvider.scrollToFirstDiff() + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diffContent, + isProtected: isWriteProtected, + } satisfies ClineSayTool) + + let toolProgressStatus + + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) + } + + const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected) + + if (!didApprove) { + await cline.diffViewProvider.revertChanges() // Cline likely handles closing the diff view + return + } + + // Call saveChanges to update the DiffViewProvider properties + await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + } // Track file edit operation if (relPath) { diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index 2b31224400..a42eb6f6f9 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -11,6 +11,7 @@ import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" import { insertGroups } from "../diff/insert-groups" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" export async function insertContentTool( cline: Task, @@ -107,15 +108,15 @@ export async function insertContentTool( }, ]).join("\n") - // Show changes in diff view - if (!cline.diffViewProvider.isEditing) { - await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) - // First open with original content - await cline.diffViewProvider.open(relPath) - await cline.diffViewProvider.update(fileContent, false) - cline.diffViewProvider.scrollToFirstDiff() - await delay(200) - } + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const diagnosticsEnabled = state?.diagnosticsEnabled ?? true + const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, + ) // For consistency with writeToFileTool, handle new files differently let diff: string | undefined @@ -135,8 +136,6 @@ export async function insertContentTool( approvalContent = updatedContent } - await cline.diffViewProvider.update(updatedContent, true) - const completeMessage = JSON.stringify({ ...sharedMessageProps, diff, @@ -150,17 +149,33 @@ export async function insertContentTool( .then((response) => response.response === "yesButtonClicked") if (!didApprove) { - await cline.diffViewProvider.revertChanges() + if (!isPreventFocusDisruptionEnabled) { + await cline.diffViewProvider.revertChanges() + } pushToolResult("Changes were rejected by the user.") return } - // Call saveChanges to update the DiffViewProvider properties - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + if (isPreventFocusDisruptionEnabled) { + // Direct file write without diff view or opening the file + await cline.diffViewProvider.saveDirectly(relPath, updatedContent, false, diagnosticsEnabled, writeDelayMs) + } else { + // Original behavior with diff view + // Show changes in diff view + if (!cline.diffViewProvider.isEditing) { + await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) + // First open with original content + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(fileContent, false) + cline.diffViewProvider.scrollToFirstDiff() + await delay(200) + } + + await cline.diffViewProvider.update(updatedContent, true) + + // Call saveChanges to update the DiffViewProvider properties + await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + } // Track file edit operation if (relPath) { diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index ec8c77a63b..80f4c9a054 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -507,11 +507,15 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} cline.consecutiveMistakeCount = 0 cline.consecutiveMistakeCountForApplyDiff.delete(relPath) - // Show diff view before asking for approval (only for single file or after batch approval) - cline.diffViewProvider.editType = "modify" - await cline.diffViewProvider.open(relPath) - await cline.diffViewProvider.update(originalContent!, true) - cline.diffViewProvider.scrollToFirstDiff() + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const diagnosticsEnabled = state?.diagnosticsEnabled ?? true + const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, + ) // For batch operations, we've already gotten approval const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false @@ -548,17 +552,35 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} } if (!didApprove) { - await cline.diffViewProvider.revertChanges() + if (!isPreventFocusDisruptionEnabled) { + await cline.diffViewProvider.revertChanges() + } results.push(`Changes to ${relPath} were not approved by user`) continue } - // Call saveChanges to update the DiffViewProvider properties - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + if (isPreventFocusDisruptionEnabled) { + // Direct file write without diff view or opening the file + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") + await cline.diffViewProvider.saveDirectly( + relPath, + originalContent!, + false, + diagnosticsEnabled, + writeDelayMs, + ) + } else { + // Original behavior with diff view + // Show diff view before asking for approval (only for single file or after batch approval) + cline.diffViewProvider.editType = "modify" + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(originalContent!, true) + cline.diffViewProvider.scrollToFirstDiff() + + // Call saveChanges to update the DiffViewProvider properties + await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + } // Track file edit operation await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource) diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index b6ec3ed39b..09b644f931 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -12,6 +12,7 @@ import { getReadablePath } from "../../utils/path" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" /** * Tool for performing search and replace operations on files @@ -199,16 +200,15 @@ export async function searchAndReplaceTool( return } - // Show changes in diff view - if (!cline.diffViewProvider.isEditing) { - await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) - await cline.diffViewProvider.open(validRelPath) - await cline.diffViewProvider.update(fileContent, false) - cline.diffViewProvider.scrollToFirstDiff() - await delay(200) - } - - await cline.diffViewProvider.update(newContent, true) + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const diagnosticsEnabled = state?.diagnosticsEnabled ?? true + const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, + ) // Request user approval for changes const completeMessage = JSON.stringify({ @@ -221,18 +221,33 @@ export async function searchAndReplaceTool( .then((response) => response.response === "yesButtonClicked") if (!didApprove) { - await cline.diffViewProvider.revertChanges() + if (!isPreventFocusDisruptionEnabled) { + await cline.diffViewProvider.revertChanges() + } pushToolResult("Changes were rejected by the user.") await cline.diffViewProvider.reset() return } - // Call saveChanges to update the DiffViewProvider properties - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + if (isPreventFocusDisruptionEnabled) { + // Direct file write without diff view or opening the file + await cline.diffViewProvider.saveDirectly(validRelPath, newContent, false, diagnosticsEnabled, writeDelayMs) + } else { + // Original behavior with diff view + // Show changes in diff view + if (!cline.diffViewProvider.isEditing) { + await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) + await cline.diffViewProvider.open(validRelPath) + await cline.diffViewProvider.update(fileContent, false) + cline.diffViewProvider.scrollToFirstDiff() + await delay(200) + } + + await cline.diffViewProvider.update(newContent, true) + + // Call saveChanges to update the DiffViewProvider properties + await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + } // Track file edit operation if (relPath) { diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index fd9d158f3f..e82eab92bc 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -1,6 +1,7 @@ import path from "path" import delay from "delay" import * as vscode from "vscode" +import fs from "fs/promises" import { Task } from "../task/Task" import { ClineSayTool } from "../../shared/ExtensionMessage" @@ -14,6 +15,7 @@ import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" export async function writeToFileTool( cline: Task, @@ -99,22 +101,32 @@ export async function writeToFileTool( try { if (block.partial) { - // update gui message - const partialMessage = JSON.stringify(sharedMessageProps) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - - // update editor - if (!cline.diffViewProvider.isEditing) { - // open the editor and prepare to stream content in - await cline.diffViewProvider.open(relPath) - } - - // editor is open, stream content in - await cline.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - false, + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, ) + if (!isPreventFocusDisruptionEnabled) { + // update gui message + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + + // update editor + if (!cline.diffViewProvider.isEditing) { + // open the editor and prepare to stream content in + await cline.diffViewProvider.open(relPath) + } + + // editor is open, stream content in + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + false, + ) + } + return } else { if (predictedLineCount === undefined) { @@ -149,76 +161,138 @@ export async function writeToFileTool( cline.consecutiveMistakeCount = 0 - // if isEditingFile false, that means we have the full contents of the file already. - // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called. - // in other words, you must always repeat the block.partial logic here - if (!cline.diffViewProvider.isEditing) { - // show gui message before showing edit animation - const partialMessage = JSON.stringify(sharedMessageProps) - await cline.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor - await cline.diffViewProvider.open(relPath) - } - - await cline.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - true, + // Check if preventFocusDisruption experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const diagnosticsEnabled = state?.diagnosticsEnabled ?? true + const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const isPreventFocusDisruptionEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, ) - await delay(300) // wait for diff view to update - cline.diffViewProvider.scrollToFirstDiff() + if (isPreventFocusDisruptionEnabled) { + // Direct file write without diff view + // Check for code omissions before proceeding + if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (cline.diffStrategy) { + pushToolResult( + formatResponse.toolError( + `Content appears to be truncated (file has ${ + newContent.split("\n").length + } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + ), + ) + return + } else { + vscode.window + .showWarningMessage( + "Potential code truncation detected. cline happens when the AI reaches its max output limit.", + "Follow cline guide to fix the issue", + ) + .then((selection) => { + if (selection === "Follow cline guide to fix the issue") { + vscode.env.openExternal( + vscode.Uri.parse( + "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", + ), + ) + } + }) + } + } - // Check for code omissions before proceeding - if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { - if (cline.diffStrategy) { - await cline.diffViewProvider.revertChanges() + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: newContent, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) - pushToolResult( - formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, - ), - ) + if (!didApprove) { return + } + + // Set up diffViewProvider properties needed for saveDirectly + cline.diffViewProvider.editType = fileExists ? "modify" : "create" + if (fileExists) { + const absolutePath = path.resolve(cline.cwd, relPath) + cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") } else { - vscode.window - .showWarningMessage( - "Potential code truncation detected. cline happens when the AI reaches its max output limit.", - "Follow cline guide to fix the issue", + cline.diffViewProvider.originalContent = "" + } + + // Save directly without showing diff view or opening the file + await cline.diffViewProvider.saveDirectly(relPath, newContent, false, diagnosticsEnabled, writeDelayMs) + } else { + // Original behavior with diff view + // if isEditingFile false, that means we have the full contents of the file already. + // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called. + // in other words, you must always repeat the block.partial logic here + if (!cline.diffViewProvider.isEditing) { + // show gui message before showing edit animation + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor + await cline.diffViewProvider.open(relPath) + } + + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + true, + ) + + await delay(300) // wait for diff view to update + cline.diffViewProvider.scrollToFirstDiff() + + // Check for code omissions before proceeding + if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (cline.diffStrategy) { + await cline.diffViewProvider.revertChanges() + + pushToolResult( + formatResponse.toolError( + `Content appears to be truncated (file has ${ + newContent.split("\n").length + } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + ), ) - .then((selection) => { - if (selection === "Follow cline guide to fix the issue") { - vscode.env.openExternal( - vscode.Uri.parse( - "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", - ), - ) - } - }) + return + } else { + vscode.window + .showWarningMessage( + "Potential code truncation detected. cline happens when the AI reaches its max output limit.", + "Follow cline guide to fix the issue", + ) + .then((selection) => { + if (selection === "Follow cline guide to fix the issue") { + vscode.env.openExternal( + vscode.Uri.parse( + "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", + ), + ) + } + }) + } } - } - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: fileExists ? undefined : newContent, - diff: fileExists - ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) - : undefined, - } satisfies ClineSayTool) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: fileExists ? undefined : newContent, + diff: fileExists + ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) + : undefined, + } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) + const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) - if (!didApprove) { - await cline.diffViewProvider.revertChanges() - return - } + if (!didApprove) { + await cline.diffViewProvider.revertChanges() + return + } - // Call saveChanges to update the DiffViewProvider properties - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + // Call saveChanges to update the DiffViewProvider properties + await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) + } // Track file edit operation if (relPath) { diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index 64820beffb..5acf09ea78 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -187,7 +187,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 @@ -222,22 +225,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() // Get diagnostic settings from state @@ -625,4 +628,99 @@ export class DiffViewProvider { this.streamedLines = [] this.preDiagnostics = [] } + + /** + * Directly save content to a file without showing diff view + * Used when preventFocusDisruption experiment is enabled + * + * @param relPath - Relative path to the file + * @param content - Content to write to the file + * @param openFile - Whether to show the file in editor (false = open in memory only for diagnostics) + * @returns Result of the save operation including any new problems detected + */ + async saveDirectly( + relPath: string, + content: string, + openFile: boolean = true, + diagnosticsEnabled: boolean = true, + writeDelayMs: number = DEFAULT_WRITE_DELAY_MS, + ): Promise<{ + newProblemsMessage: string | undefined + userEdits: string | undefined + finalContent: string | undefined + }> { + const absolutePath = path.resolve(this.cwd, relPath) + + // Get diagnostics before editing the file + this.preDiagnostics = vscode.languages.getDiagnostics() + + // Write the content directly to the file + await createDirectoriesForFile(absolutePath) + await fs.writeFile(absolutePath, content, "utf-8") + + // Open the document to ensure diagnostics are loaded + // When openFile is false (PREVENT_FOCUS_DISRUPTION enabled), we only open in memory + if (openFile) { + // Show the document in the editor + await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { + preview: false, + preserveFocus: true, + }) + } else { + // Just open the document in memory to trigger diagnostics without showing it + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(absolutePath)) + + // Save the document to ensure VSCode recognizes it as saved and triggers diagnostics + if (doc.isDirty) { + await doc.save() + } + + // Force a small delay to ensure diagnostics are triggered + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + let newProblemsMessage = "" + + if (diagnosticsEnabled) { + // Add configurable delay to allow linters time to process + const safeDelayMs = Math.max(0, writeDelayMs) + + try { + await delay(safeDelayMs) + } catch (error) { + console.warn(`Failed to apply write delay: ${error}`) + } + + const postDiagnostics = vscode.languages.getDiagnostics() + + // Get diagnostic settings from state + const task = this.taskRef.deref() + const state = await task?.providerRef.deref()?.getState() + const includeDiagnosticMessages = state?.includeDiagnosticMessages ?? true + const maxDiagnosticMessages = state?.maxDiagnosticMessages ?? 50 + + const newProblems = await diagnosticsToProblemsString( + getNewDiagnostics(this.preDiagnostics, postDiagnostics), + [vscode.DiagnosticSeverity.Error], + this.cwd, + includeDiagnosticMessages, + maxDiagnosticMessages, + ) + + newProblemsMessage = + newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : "" + } + + // Store the results for formatFileWriteResponse + this.newProblemsMessage = newProblemsMessage + this.userEdits = undefined + this.relPath = relPath + this.newContent = content + + return { + newProblemsMessage, + userEdits: undefined, + finalContent: content, + } + } } diff --git a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts index 7159aca57a..0737b143cd 100644 --- a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts +++ b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts @@ -30,6 +30,10 @@ vi.mock("vscode", () => ({ workspace: { applyEdit: vi.fn(), onDidOpenTextDocument: vi.fn(() => ({ dispose: vi.fn() })), + openTextDocument: vi.fn().mockResolvedValue({ + isDirty: false, + save: vi.fn().mockResolvedValue(undefined), + }), textDocuments: [], fs: { stat: vi.fn(), @@ -353,6 +357,88 @@ describe("DiffViewProvider", () => { }) }) + describe("saveDirectly method", () => { + beforeEach(() => { + // Mock vscode functions + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + vi.mocked(vscode.languages.getDiagnostics).mockReturnValue([]) + }) + + it("should write content directly to file without opening diff view", async () => { + const mockDelay = vi.mocked(delay) + mockDelay.mockClear() + + const result = await diffViewProvider.saveDirectly("test.ts", "new content", true, true, 2000) + + // Verify file was written + const fs = await import("fs/promises") + expect(fs.writeFile).toHaveBeenCalledWith(`${mockCwd}/test.ts`, "new content", "utf-8") + + // Verify file was opened without focus + expect(vscode.window.showTextDocument).toHaveBeenCalledWith( + expect.objectContaining({ fsPath: `${mockCwd}/test.ts` }), + { preview: false, preserveFocus: true }, + ) + + // Verify diagnostics were checked after delay + expect(mockDelay).toHaveBeenCalledWith(2000) + expect(vscode.languages.getDiagnostics).toHaveBeenCalled() + + // Verify result + expect(result.newProblemsMessage).toBe("") + expect(result.userEdits).toBeUndefined() + expect(result.finalContent).toBe("new content") + }) + + it("should not open file when openWithoutFocus is false", async () => { + await diffViewProvider.saveDirectly("test.ts", "new content", false, true, 1000) + + // Verify file was written + const fs = await import("fs/promises") + expect(fs.writeFile).toHaveBeenCalledWith(`${mockCwd}/test.ts`, "new content", "utf-8") + + // Verify file was NOT opened + expect(vscode.window.showTextDocument).not.toHaveBeenCalled() + }) + + it("should skip diagnostics when diagnosticsEnabled is false", async () => { + const mockDelay = vi.mocked(delay) + mockDelay.mockClear() + vi.mocked(vscode.languages.getDiagnostics).mockClear() + + await diffViewProvider.saveDirectly("test.ts", "new content", true, false, 1000) + + // Verify file was written + const fs = await import("fs/promises") + expect(fs.writeFile).toHaveBeenCalledWith(`${mockCwd}/test.ts`, "new content", "utf-8") + + // Verify delay was NOT called + expect(mockDelay).not.toHaveBeenCalled() + // getDiagnostics is called once for pre-diagnostics, but not for post-diagnostics + expect(vscode.languages.getDiagnostics).toHaveBeenCalledTimes(1) + }) + + it("should handle negative delay values", async () => { + const mockDelay = vi.mocked(delay) + mockDelay.mockClear() + + await diffViewProvider.saveDirectly("test.ts", "new content", true, true, -500) + + // Verify delay was called with 0 (safe minimum) + expect(mockDelay).toHaveBeenCalledWith(0) + }) + + it("should store results for formatFileWriteResponse", async () => { + await diffViewProvider.saveDirectly("test.ts", "new content", true, true, 1000) + + // Verify internal state was updated + expect((diffViewProvider as any).newProblemsMessage).toBe("") + expect((diffViewProvider as any).userEdits).toBeUndefined() + expect((diffViewProvider as any).relPath).toBe("test.ts") + expect((diffViewProvider as any).newContent).toBe("new content") + }) + }) + describe("saveChanges method with diagnostic settings", () => { beforeEach(() => { // Setup common mocks for saveChanges tests diff --git a/src/shared/__tests__/experiments-preventFocusDisruption.spec.ts b/src/shared/__tests__/experiments-preventFocusDisruption.spec.ts new file mode 100644 index 0000000000..7e5389c286 --- /dev/null +++ b/src/shared/__tests__/experiments-preventFocusDisruption.spec.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from "vitest" +import { EXPERIMENT_IDS, experimentConfigsMap, experimentDefault, experiments } from "../experiments" + +describe("PREVENT_FOCUS_DISRUPTION experiment", () => { + it("should include PREVENT_FOCUS_DISRUPTION in EXPERIMENT_IDS", () => { + expect(EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION).toBe("preventFocusDisruption") + }) + + it("should have PREVENT_FOCUS_DISRUPTION in experimentConfigsMap", () => { + expect(experimentConfigsMap.PREVENT_FOCUS_DISRUPTION).toBeDefined() + expect(experimentConfigsMap.PREVENT_FOCUS_DISRUPTION.enabled).toBe(false) + }) + + it("should have PREVENT_FOCUS_DISRUPTION in experimentDefault", () => { + expect(experimentDefault.preventFocusDisruption).toBe(false) + }) + + it("should correctly check if PREVENT_FOCUS_DISRUPTION is enabled", () => { + // Test when experiment is disabled (default) + const disabledConfig = { preventFocusDisruption: false } + expect(experiments.isEnabled(disabledConfig, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) + + // Test when experiment is enabled + const enabledConfig = { preventFocusDisruption: true } + expect(experiments.isEnabled(enabledConfig, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(true) + + // Test when experiment is not in config (should use default) + const emptyConfig = {} + expect(experiments.isEnabled(emptyConfig, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) + }) +}) diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 4a8f06d62a..607c1e0b04 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -28,6 +28,7 @@ describe("experiments", () => { const experiments: Record = { 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 = { 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 = { powerSteering: false, multiFileApplyDiff: false, + preventFocusDisruption: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 1edadf654f..548b55f68c 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -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 type _AssertExperimentIds = AssertEqual>> @@ -16,6 +17,7 @@ interface ExperimentConfig { export const experimentConfigsMap: Record = { MULTI_FILE_APPLY_DIFF: { enabled: false }, POWER_STEERING: { enabled: false }, + PREVENT_FOCUS_DISRUPTION: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 1e5867d3fc..54e46d2c7b 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -226,6 +226,7 @@ describe("mergeExtensionState", () => { disableCompletionCommand: false, concurrentFileReads: true, multiFileApplyDiff: true, + preventFocusDisruption: false, } as Record, } @@ -242,6 +243,7 @@ describe("mergeExtensionState", () => { disableCompletionCommand: false, concurrentFileReads: true, multiFileApplyDiff: true, + preventFocusDisruption: false, }) }) }) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index ab929d724d..ae5a66d0d0 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -648,6 +648,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Habilita edicions de fitxers concurrents", "description": "Quan està activat, Roo pot editar múltiples fitxers en una sola petició. Quan està desactivat, Roo ha d'editar fitxers d'un en un. Desactivar això pot ajudar quan es treballa amb models menys capaços o quan vols més control sobre les modificacions de fitxers." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Edició en segon pla", + "description": "Quan s'activa, evita la interrupció del focus de l'editor. Les edicions de fitxers es produeixen en segon pla sense obrir la vista diff o robar el focus. Pots continuar treballant sense interrupcions mentre Roo fa canvis. Els fitxers poden obrir-se sense focus per capturar diagnòstics o romandre completament tancats." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 766bc891e4..2dc6c21eb4 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -648,6 +648,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Gleichzeitige Dateibearbeitungen aktivieren", "description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage bearbeiten. Wenn deaktiviert, muss Roo Dateien einzeln bearbeiten. Das Deaktivieren kann hilfreich sein, wenn mit weniger fähigen Modellen gearbeitet wird oder wenn du mehr Kontrolle über Dateiänderungen haben möchtest." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Hintergrundbearbeitung", + "description": "Verhindert Editor-Fokus-Störungen wenn aktiviert. Dateibearbeitungen erfolgen im Hintergrund ohne Öffnung von Diff-Ansichten oder Fokus-Diebstahl. Du kannst ungestört weiterarbeiten, während Roo Änderungen vornimmt. Dateien können ohne Fokus geöffnet werden, um Diagnosen zu erfassen oder vollständig geschlossen bleiben." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index cfd5b04286..acd07b0335 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -648,6 +648,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": "Background editing", + "description": "Prevent editor focus disruption when enabled. File edits happen in the background without opening diff views or stealing focus. You can continue working uninterrupted while Roo makes changes. Files can be opened without focus to capture diagnostics or kept closed entirely." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index f536863909..7ad549b070 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -648,6 +648,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Habilitar ediciones de archivos concurrentes", "description": "Cuando está habilitado, Roo puede editar múltiples archivos en una sola solicitud. Cuando está deshabilitado, Roo debe editar archivos de uno en uno. Deshabilitar esto puede ayudar cuando trabajas con modelos menos capaces o cuando quieres más control sobre las modificaciones de archivos." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Edición en segundo plano", + "description": "Previene la interrupción del foco del editor cuando está habilitado. Las ediciones de archivos ocurren en segundo plano sin abrir vistas de diferencias o robar el foco. Puedes continuar trabajando sin interrupciones mientras Roo realiza cambios. Los archivos pueden abrirse sin foco para capturar diagnósticos o mantenerse completamente cerrados." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 0e12c58d38..57693015ad 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -648,6 +648,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Activer les éditions de fichiers concurrentes", "description": "Lorsque cette option est activée, Roo peut éditer plusieurs fichiers en une seule requête. Lorsqu'elle est désactivée, Roo doit éditer les fichiers un par un. Désactiver cette option peut aider lorsque tu travailles avec des modèles moins capables ou lorsque tu veux plus de contrôle sur les modifications de fichiers." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Édition en arrière-plan", + "description": "Empêche la perturbation du focus de l'éditeur lorsqu'activé. Les modifications de fichiers se font en arrière-plan sans ouvrir de vues de différences ou voler le focus. Vous pouvez continuer à travailler sans interruption pendant que Roo effectue des changements. Les fichiers peuvent être ouverts sans focus pour capturer les diagnostics ou rester complètement fermés." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index d2cfa971ff..8bc9116ab5 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "समानांतर फ़ाइल संपादन सक्षम करें", "description": "जब सक्षम किया जाता है, तो Roo एक ही अनुरोध में कई फ़ाइलों को संपादित कर सकता है। जब अक्षम किया जाता है, तो Roo को एक समय में एक फ़ाइल संपादित करनी होगी। इसे अक्षम करना तब मदद कर सकता है जब आप कम सक्षम मॉडल के साथ काम कर रहे हों या जब आप फ़ाइल संशोधनों पर अधिक नियंत्रण चाहते हों।" + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "बैकग्राउंड संपादन", + "description": "सक्षम होने पर एडिटर फोकस व्यवधान को रोकता है। फ़ाइल संपादन diff व्यू खोले बिना या फोकस चुराए बिना बैकग्राउंड में होता है। आप Roo के बदलाव करते समय बिना किसी बाधा के काम जारी रख सकते हैं। फ़ाइलें डायग्नोस्टिक्स कैप्चर करने के लिए बिना फोकस के खुल सकती हैं या पूरी तरह बंद रह सकती हैं।" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 3f362f650d..2568abf68f 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -678,6 +678,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Aktifkan edit file bersamaan", "description": "Ketika diaktifkan, Roo dapat mengedit beberapa file dalam satu permintaan. Ketika dinonaktifkan, Roo harus mengedit file satu per satu. Menonaktifkan ini dapat membantu saat bekerja dengan model yang kurang mampu atau ketika kamu ingin kontrol lebih terhadap modifikasi file." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Pengeditan Latar Belakang", + "description": "Ketika diaktifkan, mencegah gangguan fokus editor. Pengeditan file terjadi di latar belakang tanpa membuka tampilan diff atau mencuri fokus. Anda dapat terus bekerja tanpa gangguan saat Roo melakukan perubahan. File mungkin dibuka tanpa fokus untuk menangkap diagnostik atau tetap tertutup sepenuhnya." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 14deda20ea..784fc6bc45 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Abilita modifiche di file concorrenti", "description": "Quando abilitato, Roo può modificare più file in una singola richiesta. Quando disabilitato, Roo deve modificare i file uno alla volta. Disabilitare questa opzione può aiutare quando lavori con modelli meno capaci o quando vuoi più controllo sulle modifiche dei file." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Modifica in background", + "description": "Previene l'interruzione del focus dell'editor quando abilitato. Le modifiche ai file avvengono in background senza aprire viste di differenze o rubare il focus. Puoi continuare a lavorare senza interruzioni mentre Roo effettua modifiche. I file possono essere aperti senza focus per catturare diagnostiche o rimanere completamente chiusi." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index d7864e40db..d5ba882ce9 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "同時ファイル編集を有効にする", "description": "有効にすると、Rooは単一のリクエストで複数のファイルを編集できます。無効にすると、Rooはファイルを一つずつ編集する必要があります。これを無効にすることで、能力の低いモデルで作業する場合や、ファイル変更をより細かく制御したい場合に役立ちます。" + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "バックグラウンド編集", + "description": "有効にすると、エディターのフォーカス中断を防ぎます。ファイル編集は差分ビューを開いたりフォーカスを奪ったりすることなく、バックグラウンドで行われます。Rooが変更を行っている間も中断されることなく作業を続けることができます。ファイルは診断をキャプチャするためにフォーカスなしで開くか、完全に閉じたままにできます。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index adbe216463..11b5211cc9 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "동시 파일 편집 활성화", "description": "활성화하면 Roo가 단일 요청으로 여러 파일을 편집할 수 있습니다. 비활성화하면 Roo는 파일을 하나씩 편집해야 합니다. 이 기능을 비활성화하면 덜 강력한 모델로 작업하거나 파일 수정에 대한 더 많은 제어가 필요할 때 도움이 됩니다." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "백그라운드 편집", + "description": "활성화하면 편집기 포커스 방해를 방지합니다. 파일 편집이 diff 뷰를 열거나 포커스를 빼앗지 않고 백그라운드에서 수행됩니다. Roo가 변경사항을 적용하는 동안 방해받지 않고 계속 작업할 수 있습니다. 파일은 진단을 캡처하기 위해 포커스 없이 열거나 완전히 닫힌 상태로 유지할 수 있습니다." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 6085be8fb1..13eabe36ab 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Gelijktijdige bestandsbewerkingen inschakelen", "description": "Wanneer ingeschakeld, kan Roo meerdere bestanden in één verzoek bewerken. Wanneer uitgeschakeld, moet Roo bestanden één voor één bewerken. Het uitschakelen hiervan kan helpen wanneer je werkt met minder capabele modellen of wanneer je meer controle wilt over bestandswijzigingen." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Achtergrondbewerking", + "description": "Voorkomt editor focus verstoring wanneer ingeschakeld. Bestandsbewerkingen gebeuren op de achtergrond zonder diff-weergaven te openen of focus te stelen. Je kunt ononderbroken doorwerken terwijl Roo wijzigingen aanbrengt. Bestanden kunnen zonder focus worden geopend om diagnostiek vast te leggen of volledig gesloten blijven." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 9107fd959e..ef37b25546 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Włącz równoczesne edycje plików", "description": "Gdy włączone, Roo może edytować wiele plików w jednym żądaniu. Gdy wyłączone, Roo musi edytować pliki jeden po drugim. Wyłączenie tego może pomóc podczas pracy z mniej zdolnymi modelami lub gdy chcesz mieć większą kontrolę nad modyfikacjami plików." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Edycja w tle", + "description": "Zapobiega zakłócaniu fokusa edytora gdy włączone. Edycje plików odbywają się w tle bez otwierania widoków różnic lub kradzieży fokusa. Możesz kontynuować pracę bez przeszkód podczas gdy Roo wprowadza zmiany. Pliki mogą być otwierane bez fokusa aby przechwycić diagnostykę lub pozostać całkowicie zamknięte." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 8137d917ae..c26c532844 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Habilitar edições de arquivos concorrentes", "description": "Quando habilitado, o Roo pode editar múltiplos arquivos em uma única solicitação. Quando desabilitado, o Roo deve editar arquivos um de cada vez. Desabilitar isso pode ajudar ao trabalhar com modelos menos capazes ou quando você quer mais controle sobre modificações de arquivos." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Edição em segundo plano", + "description": "Previne a interrupção do foco do editor quando habilitado. As edições de arquivos acontecem em segundo plano sem abrir visualizações de diferenças ou roubar o foco. Você pode continuar trabalhando sem interrupções enquanto o Roo faz alterações. Os arquivos podem ser abertos sem foco para capturar diagnósticos ou permanecer completamente fechados." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index e93f24e9d7..f5e9cd8ad3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Включить одновременное редактирование файлов", "description": "Когда включено, Roo может редактировать несколько файлов в одном запросе. Когда отключено, Roo должен редактировать файлы по одному. Отключение этой функции может помочь при работе с менее способными моделями или когда вы хотите больше контроля над изменениями файлов." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Фоновое редактирование", + "description": "Предотвращает нарушение фокуса редактора при включении. Редактирование файлов происходит в фоновом режиме без открытия представлений различий или кражи фокуса. Вы можете продолжать работать без перерывов, пока Roo вносит изменения. Файлы могут открываться без фокуса для захвата диагностики или оставаться полностью закрытыми." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 450738183b..e7a4526672 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Eşzamanlı dosya düzenlemelerini etkinleştir", "description": "Etkinleştirildiğinde, Roo tek bir istekte birden fazla dosyayı düzenleyebilir. Devre dışı bırakıldığında, Roo dosyaları tek tek düzenlemek zorundadır. Bunu devre dışı bırakmak, daha az yetenekli modellerle çalışırken veya dosya değişiklikleri üzerinde daha fazla kontrol istediğinde yardımcı olabilir." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Arka plan düzenleme", + "description": "Etkinleştirildiğinde editör odak kesintisini önler. Dosya düzenlemeleri diff görünümlerini açmadan veya odağı çalmadan arka planda gerçekleşir. Roo değişiklikler yaparken kesintisiz çalışmaya devam edebilirsiniz. Dosyalar tanılamayı yakalamak için odaksız açılabilir veya tamamen kapalı kalabilir." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 87e99ce6f5..d3235da82f 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "Bật chỉnh sửa tệp đồng thời", "description": "Khi được bật, Roo có thể chỉnh sửa nhiều tệp trong một yêu cầu duy nhất. Khi bị tắt, Roo phải chỉnh sửa từng tệp một. Tắt tính năng này có thể hữu ích khi làm việc với các mô hình kém khả năng hơn hoặc khi bạn muốn kiểm soát nhiều hơn đối với các thay đổi tệp." + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "Chỉnh sửa nền", + "description": "Khi được bật, ngăn chặn gián đoạn tiêu điểm trình soạn thảo. Việc chỉnh sửa tệp diễn ra ở nền mà không mở chế độ xem diff hoặc chiếm tiêu điểm. Bạn có thể tiếp tục làm việc không bị gián đoạn trong khi Roo thực hiện thay đổi. Các tệp có thể được mở mà không có tiêu điểm để thu thập chẩn đoán hoặc giữ hoàn toàn đóng." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index e94c857e65..87990e6890 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "启用并发文件编辑", "description": "启用后 Roo 可在单个请求中编辑多个文件。禁用后 Roo 必须逐个编辑文件。禁用此功能有助于使用能力较弱的模型或需要更精确控制文件修改时。" + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "后台编辑", + "description": "启用后防止编辑器焦点干扰。文件编辑在后台进行,不会打开差异视图或抢夺焦点。你可以在 Roo 进行更改时继续不受干扰地工作。文件可以在不获取焦点的情况下打开以捕获诊断信息,或保持完全关闭状态。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index fa10cf3028..14d4840b98 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -649,6 +649,10 @@ "MULTI_FILE_APPLY_DIFF": { "name": "啟用並行檔案編輯", "description": "啟用後 Roo 可在單個請求中編輯多個檔案。停用後 Roo 必須逐個編輯檔案。停用此功能有助於使用能力較弱的模型或需要更精確控制檔案修改時。" + }, + "PREVENT_FOCUS_DISRUPTION": { + "name": "背景編輯", + "description": "啟用後可防止編輯器焦點中斷。檔案編輯會在背景進行,不會開啟 diff 檢視或搶奪焦點。您可以在 Roo 進行變更時繼續不受干擾地工作。檔案可能會在不獲得焦點的情況下開啟以捕獲診斷,或保持完全關閉。" } }, "promptCaching": {