diff --git a/.changeset/small-snakes-suffer.md b/.changeset/small-snakes-suffer.md new file mode 100644 index 0000000000..9d0a607091 --- /dev/null +++ b/.changeset/small-snakes-suffer.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Added an editable setting to allow users to control the closing of diff tab and corresponding file. diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 514b15d783..d5990156d8 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -87,6 +87,8 @@ export const globalSettingsSchema = z.object({ rateLimitSeconds: z.number().optional(), diffEnabled: z.boolean().optional(), + autoCloseRooTabs: z.boolean().optional(), + autoCloseAllRooTabs: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), experiments: experimentsSchema.optional(), diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c8553a8fc6..183f881120 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1118,6 +1118,8 @@ export class Task extends EventEmitter { private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise { // Kicks off the checkpoints initialization process in the background. getCheckpointService(this) + // Initialize the diff view provider for this task. + this.diffViewProvider.initialize() let nextUserContent = userContent let includeFileDetails = true diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 12fd9dd349..0bd3b1977c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1380,6 +1380,8 @@ export class ClineProvider ttsEnabled, ttsSpeed, diffEnabled, + autoCloseRooTabs, + autoCloseAllRooTabs, enableCheckpoints, taskHistory, soundVolume, @@ -1476,6 +1478,8 @@ export class ClineProvider ttsEnabled: ttsEnabled ?? false, ttsSpeed: ttsSpeed ?? 1.0, diffEnabled: diffEnabled ?? true, + autoCloseRooTabs: autoCloseRooTabs ?? false, + autoCloseAllRooTabs: autoCloseAllRooTabs ?? false, enableCheckpoints: enableCheckpoints ?? true, shouldShowAnnouncement: telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId, @@ -1642,6 +1646,8 @@ export class ClineProvider ttsEnabled: stateValues.ttsEnabled ?? false, ttsSpeed: stateValues.ttsSpeed ?? 1.0, diffEnabled: stateValues.diffEnabled ?? true, + autoCloseRooTabs: stateValues.autoCloseRooTabs ?? false, + autoCloseAllRooTabs: stateValues.autoCloseAllRooTabs ?? false, enableCheckpoints: stateValues.enableCheckpoints ?? true, soundVolume: stateValues.soundVolume, browserViewportSize: stateValues.browserViewportSize ?? "900x600", diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index e70b39df8f..979bb45745 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -251,6 +251,26 @@ export const webviewMessageHandler = async ( } switch (message.type) { + case "autoCloseRooTabs": + const autoCloseRooTabs = message.bool ?? false + await provider.context.globalState.update("autoCloseRooTabs", autoCloseRooTabs) + // Also update workspace settings + await vscode.workspace + .getConfiguration("roo-cline") + .update("autoCloseRooTabs", autoCloseRooTabs, vscode.ConfigurationTarget.Global) + await updateGlobalState("autoCloseRooTabs", autoCloseRooTabs) + await provider.postStateToWebview() + break + case "autoCloseAllRooTabs": + const autoCloseAllRooTabs = message.bool ?? false + await provider.context.globalState.update("autoCloseAllRooTabs", autoCloseAllRooTabs) + // Also update workspace settings + await vscode.workspace + .getConfiguration("roo-cline") + .update("autoCloseAllRooTabs", autoCloseAllRooTabs, vscode.ConfigurationTarget.Global) + await updateGlobalState("autoCloseAllRooTabs", autoCloseAllRooTabs) + await provider.postStateToWebview() + break case "webviewDidLaunch": // Load custom modes first const customModes = await provider.customModesManager.getCustomModes() diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index 225e076297..9dcd7cdd56 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -13,10 +13,17 @@ import { ClineSayTool } from "../../shared/ExtensionMessage" import { Task } from "../../core/task/Task" import { DecorationController } from "./DecorationController" +import { PostDiffViewBehaviorUtils } from "./PostDiffViewBehaviorUtils" +import { TextDocument, TextDocumentShowOptions } from "vscode" export const DIFF_VIEW_URI_SCHEME = "cline-diff" export const DIFF_VIEW_LABEL_CHANGES = "Original ↔ Roo's Changes" +interface DiffSettings { + autoCloseRooTabs: boolean + autoCloseAllRooTabs: boolean +} + // TODO: https://github.com/cline/cline/pull/3354 export class DiffViewProvider { // Properties to store the results of saveChanges @@ -34,15 +41,88 @@ export class DiffViewProvider { private activeLineController?: DecorationController private streamedLines: string[] = [] private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [] + private rooOpenedTabs: Set = new Set() + private autoCloseAllRooTabs: boolean = false // Added new setting + private preDiffActiveEditor?: vscode.TextEditor // Store active editor before diff operation + private postDiffBehaviorUtils: PostDiffViewBehaviorUtils + + constructor(private cwd: string) { + // Initialize PostDiffviewBehaviorUtils with initial context + this.postDiffBehaviorUtils = new PostDiffViewBehaviorUtils({ + relPath: this.relPath, + editType: this.editType, + documentWasOpen: this.documentWasOpen, + cwd: this.cwd, + rooOpenedTabs: this.rooOpenedTabs, + preDiffActiveEditor: this.preDiffActiveEditor, + autoCloseAllRooTabs: this.autoCloseAllRooTabs, + }) + } - constructor(private cwd: string) {} + public initialize() { + const settings = this._readDiffSettings() + this.autoCloseAllRooTabs = settings.autoCloseAllRooTabs + // Track currently visible editors and active editor for focus restoration and tab cleanup + this.rooOpenedTabs.clear() + + // Update PostDiffviewBehaviorUtils context with latest values + this.postDiffBehaviorUtils.updateContext({ + autoCloseAllRooTabs: this.autoCloseAllRooTabs, + }) + } + + private _readDiffSettings(): DiffSettings { + const config = vscode.workspace.getConfiguration("roo-cline") + const autoCloseRooTabs = config.get("autoCloseRooTabs", false) + const autoCloseAllRooTabs = config.get("autoCloseAllRooTabs", false) + return { autoCloseRooTabs, autoCloseAllRooTabs } + } + + private async showTextDocumentSafe({ + uri, + textDocument, + options, + }: { + uri?: vscode.Uri + textDocument?: TextDocument + options?: TextDocumentShowOptions + }) { + // If the uri is already open, we want to focus it + if (uri) { + const editor = await vscode.window.showTextDocument(uri, options) + return editor + } + // If the textDocument is already open, we want to focus it + if (textDocument) { + const editor = await vscode.window.showTextDocument(textDocument, options) + return editor + } + // If the textDocument is not open and not able to be opened, we just reset the suppressInteractionFlag + return null + } async open(relPath: string): Promise { + this.preDiffActiveEditor = vscode.window.activeTextEditor + this.relPath = relPath const fileExists = this.editType === "modify" const absolutePath = path.resolve(this.cwd, relPath) this.isEditing = true + // Update PostDiffviewBehaviorUtils context with current state + this.postDiffBehaviorUtils.updateContext({ + relPath: relPath, + editType: this.editType, + documentWasOpen: this.documentWasOpen, + preDiffActiveEditor: this.preDiffActiveEditor, + }) + + // Track the URI of the actual file that will be part of the diff. + // This ensures that if VS Code opens a tab for it during vscode.diff, + // we can identify it as a "Roo-opened" tab for cleanup. + const fileUriForDiff = vscode.Uri.file(absolutePath) + this.rooOpenedTabs.add(fileUriForDiff.toString()) + // If the file is already open, ensure it's not dirty before getting its // contents. if (fileExists) { @@ -76,22 +156,18 @@ export class DiffViewProvider { // If the file was already open, close it (must happen after showing the // diff view since if it's the only tab the column will close). - this.documentWasOpen = false - - // Close the tab if it's open (it's already saved above). - const tabs = vscode.window.tabGroups.all - .map((tg) => tg.tabs) - .flat() - .filter( - (tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath), - ) - - for (const tab of tabs) { - if (!tab.isDirty) { - await vscode.window.tabGroups.close(tab) - } - this.documentWasOpen = true - } + this.documentWasOpen = + vscode.window.tabGroups.all + .map((tg) => tg.tabs) + .flat() + .filter( + (tab) => + tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath), + ).length > 0 + + this.postDiffBehaviorUtils.updateContext({ + documentWasOpen: this.documentWasOpen, + }) this.activeDiffEditor = await this.openDiffEditor() this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor) @@ -102,6 +178,17 @@ export class DiffViewProvider { this.streamedLines = [] } + /** + * Opens a file editor and tracks it as opened by Roo if not already open. + */ + private async showAndTrackEditor(uri: vscode.Uri, options: vscode.TextDocumentShowOptions = {}) { + const editor = await this.showTextDocumentSafe({ uri, options }) + // Always track tabs opened by Roo, regardless of autoCloseTabs setting or if the document was already open. + // The decision to close will be made in closeAllRooOpenedViews based on settings. + this.rooOpenedTabs.add(uri.toString()) + return editor + } + async update(accumulatedContent: string, isFinal: boolean) { if (!this.relPath || !this.activeLineController || !this.fadedOverlayController) { throw new Error("Required values not set") @@ -197,8 +284,27 @@ export class DiffViewProvider { } await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true }) - await this.closeAllDiffViews() + await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings()) + + // Implement post-diff focus behavior + await this.postDiffBehaviorUtils.handlePostDiffFocus() + + // If no auto-close settings are enabled and the document was not open before, + // open the file after the diff is complete. + + const settings = this._readDiffSettings() // Dynamically read settings + + // If no auto-close settings are enabled and the document was not open before OR it's a new file, + // open the file after the diff is complete. + if ( + !settings.autoCloseRooTabs && + !settings.autoCloseAllRooTabs && + (this.editType === "create" || !this.documentWasOpen) + ) { + const absolutePath = path.resolve(this.cwd, this.relPath!) + await this.showAndTrackEditor(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true }) + } // Getting diagnostics before and after the file edit is a better approach than // automatically tracking problems in real-time. This method ensures we only // report new problems that are a direct result of this specific edit. @@ -344,7 +450,7 @@ export class DiffViewProvider { await updatedDocument.save() } - await this.closeAllDiffViews() + await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings()) await fs.unlink(absolutePath) // Remove only the directories we created, in reverse order. @@ -375,47 +481,19 @@ export class DiffViewProvider { }) } - await this.closeAllDiffViews() + await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings()) } + // Implement post-diff focus behavior + await this.postDiffBehaviorUtils.handlePostDiffFocus() + // Edit is done. await this.reset() } - private async closeAllDiffViews(): Promise { - const closeOps = vscode.window.tabGroups.all - .flatMap((group) => group.tabs) - .filter((tab) => { - // Check for standard diff views with our URI scheme - if ( - tab.input instanceof vscode.TabInputTextDiff && - tab.input.original.scheme === DIFF_VIEW_URI_SCHEME && - !tab.isDirty - ) { - return true - } - - // Also check by tab label for our specific diff views - // This catches cases where the diff view might be created differently - // when files are pre-opened as text documents - if (tab.label.includes(DIFF_VIEW_LABEL_CHANGES) && !tab.isDirty) { - return true - } - - return false - }) - .map((tab) => - vscode.window.tabGroups.close(tab).then( - () => undefined, - (err) => { - console.error(`Failed to close diff tab ${tab.label}`, err) - }, - ), - ) - - await Promise.all(closeOps) - } - + /** + * Opens the diff editor + */ private async openDiffEditor(): Promise { if (!this.relPath) { throw new Error( @@ -580,7 +658,13 @@ export class DiffViewProvider { } async reset(): Promise { - await this.closeAllDiffViews() + // Ensure any diff views opened by this provider are closed to release + // memory. + try { + await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings()) + } catch (error) { + console.error("Error closing diff views", error) + } this.editType = undefined this.isEditing = false this.originalContent = undefined @@ -591,5 +675,7 @@ export class DiffViewProvider { this.activeLineController = undefined this.streamedLines = [] this.preDiagnostics = [] + this.rooOpenedTabs.clear() + } } diff --git a/src/integrations/editor/PostDiffViewBehaviorUtils.ts b/src/integrations/editor/PostDiffViewBehaviorUtils.ts new file mode 100644 index 0000000000..b70f93eb3b --- /dev/null +++ b/src/integrations/editor/PostDiffViewBehaviorUtils.ts @@ -0,0 +1,306 @@ +import * as vscode from "vscode" +import * as path from "path" +import { arePathsEqual } from "../../utils/path" +import { DIFF_VIEW_URI_SCHEME } from "./DiffViewProvider" + +/** + * Interface for diff settings used by PostDiffviewBehaviorUtils + */ +interface DiffSettings { + autoCloseRooTabs: boolean + autoCloseAllRooTabs: boolean +} + +/** + * Context object containing the state needed for post-diff behavior + */ +interface PostDiffContext { + relPath?: string + editType?: "create" | "modify" + documentWasOpen: boolean + cwd: string + rooOpenedTabs: Set + preDiffActiveEditor?: vscode.TextEditor + autoCloseAllRooTabs: boolean +} + +/** + * Utility class for handling post-diff behavior including tab management and focus restoration. + * This class encapsulates all logic related to what happens after a diff operation is completed. + */ +export class PostDiffViewBehaviorUtils { + private context: PostDiffContext + + constructor(context: PostDiffContext) { + this.context = context + } + + /** + * Updates the context with new values + * @param updates Partial context updates + */ + public updateContext(updates: Partial): void { + this.context = { ...this.context, ...updates } + } + + /** + * Handles post-diff focus behavior. + * Currently defaults to focusing the edited file (Behavior A). + * Future implementation will support configurable focus behavior. + */ + public async handlePostDiffFocus(): Promise { + if (!this.context.relPath) { + return + } + + if (this.context.autoCloseAllRooTabs) { + // Focus on the pre-diff active tab + await this.focusOnPreDiffActiveTab() + return + } + // Focus on the edited file (temporary default) + await this.focusOnEditedFile() + } + + /** + * Focuses on the tab of the file that was just edited. + */ + public async focusOnEditedFile(): Promise { + if (!this.context.relPath) { + return + } + + try { + const absolutePath = path.resolve(this.context.cwd, this.context.relPath) + const fileUri = vscode.Uri.file(absolutePath) + + // Check if the file still exists as a tab + const editedFileTab = this.findTabForFile(absolutePath) + if (editedFileTab) { + // Find the tab group containing the edited file + const tabGroup = vscode.window.tabGroups.all.find((group) => + group.tabs.some((tab) => tab === editedFileTab), + ) + + if (tabGroup) { + // Make the edited file's tab active + await this.showTextDocumentSafe({ + uri: fileUri, + options: { + viewColumn: tabGroup.viewColumn, + preserveFocus: false, + preview: false, + }, + }) + } + } + } catch (error) { + console.error("Roo Debug: Error focusing on edited file:", error) + } + } + + /** + * Restores focus to the tab that was active before the diff operation. + * This method is prepared for future use when configurable focus behavior is implemented. + */ + public async focusOnPreDiffActiveTab(): Promise { + if (!this.context.preDiffActiveEditor || !this.context.preDiffActiveEditor.document) { + return + } + + try { + // Check if the pre-diff active editor is still valid and its document is still open + const isDocumentStillOpen = vscode.workspace.textDocuments.some( + (doc) => doc === this.context.preDiffActiveEditor!.document, + ) + + if (isDocumentStillOpen) { + // Restore focus to the pre-diff active editor + await vscode.window.showTextDocument(this.context.preDiffActiveEditor.document.uri, { + viewColumn: this.context.preDiffActiveEditor.viewColumn, + preserveFocus: false, + preview: false, + }) + } + } catch (error) { + console.error("Roo Debug: Error restoring focus to pre-diff active tab:", error) + } + } + + /** + * Determines whether a tab should be closed based on the diff settings and tab characteristics. + * @param tab The VSCode tab to evaluate + * @param settings The diff settings containing auto-close preferences + * @returns True if the tab should be closed, false otherwise + */ + public tabToCloseFilter(tab: vscode.Tab, settings: DiffSettings): boolean { + // Always close DiffView tabs opened by Roo + if (tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME) { + return true + } + + let isRooOpenedTextTab = false + if (tab.input instanceof vscode.TabInputText) { + const currentTabUri = (tab.input as vscode.TabInputText).uri + for (const openedUriString of this.context.rooOpenedTabs) { + try { + const previouslyOpenedUri = vscode.Uri.parse(openedUriString, true) // true for strict parsing + if (currentTabUri.scheme === "file" && previouslyOpenedUri.scheme === "file") { + if (arePathsEqual(currentTabUri.fsPath, previouslyOpenedUri.fsPath)) { + isRooOpenedTextTab = true + break + } + } else { + if (currentTabUri.toString() === previouslyOpenedUri.toString()) { + isRooOpenedTextTab = true + break + } + } + } catch (e) { + // Log parsing error if necessary, or ignore if a URI in rooOpenedTabs is malformed + console.error(`Roo Debug: Error parsing URI from rooOpenedTabs: ${openedUriString}`, e) + } + } + } + + if (!isRooOpenedTextTab) { + return false // Not a text tab or not identified as opened by Roo + } + + // Checkmark 2 (settings.autoCloseAllRooTabs) - takes precedence + if (settings.autoCloseAllRooTabs) { + // This implies Checkmark 1 is also effectively on + return true // Close all Roo-opened text tabs + } + + // Only Checkmark 1 (settings.autoCloseRooTabs) is on, Checkmark 2 is off + if (settings.autoCloseRooTabs) { + const tabUriFsPath = (tab.input as vscode.TabInputText).uri.fsPath + const absolutePathDiffedFile = this.context.relPath + ? path.resolve(this.context.cwd, this.context.relPath) + : null + + // Guard against null absolutePathDiffedFile if relPath is somehow not set + if (!absolutePathDiffedFile) { + // If we don't know the main diffed file, but Checkmark 1 is on, + // it's safer to close any tab Roo opened to avoid leaving extras. + return true + } + + const isMainDiffedFileTab = arePathsEqual(tabUriFsPath, absolutePathDiffedFile) + + if (this.context.editType === "create" && isMainDiffedFileTab) { + return true // Case: New file, Checkmark 1 is on -> Close its tab. + } + + if (this.context.editType === "modify" && isMainDiffedFileTab) { + return !this.context.documentWasOpen + } + + // If the tab is for a file OTHER than the main diffedFile, but was opened by Roo + if (!isMainDiffedFileTab) { + // This covers scenarios where Roo might open auxiliary files (though less common for single diff). + // If Checkmark 1 is on, these should also be closed. + return true + } + } + return false // Default: do not close if no above condition met + } + + /** + * Closes a single tab with error handling and fresh reference lookup. + * @param tab The tab to close + */ + public async closeTab(tab: vscode.Tab): Promise { + // If a tab has made it through the filter, it means one of the auto-close settings + // (autoCloseTabs or autoCloseAllRooTabs) is active and the conditions for closing + // this specific tab are met. Therefore, we should always bypass the dirty check. + // const bypassDirtyCheck = true; // This is implicitly true now. + + // Attempt to find the freshest reference to the tab before closing, + // as the original 'tab' object from the initial flatMap might be stale. + const tabInputToClose = tab.input + const freshTabToClose = vscode.window.tabGroups.all + .flatMap((group) => group.tabs) + .find((t) => t.input === tabInputToClose) + + if (freshTabToClose) { + try { + await vscode.window.tabGroups.close(freshTabToClose, true) // true to bypass dirty check implicitly + } catch (closeError) { + console.error(`Roo Debug CloseLoop: Error closing tab "${freshTabToClose.label}":`, closeError) + } + } else { + // This case should ideally not happen if the tab was in the filtered list. + // It might indicate the tab was closed by another means or its input changed. + console.warn( + `Roo Debug CloseLoop: Tab "${tab.label}" (input: ${JSON.stringify(tab.input)}) intended for closure was not found in the current tab list.`, + ) + // Fallback: Try to close the original tab reference if the fresh one isn't found, + // though this is less likely to succeed if it's genuinely stale. + try { + console.log(`Roo Debug CloseLoop: Attempting to close original (stale?) tab "${tab.label}"`) + await vscode.window.tabGroups.close(tab, true) + } catch (fallbackCloseError) { + console.error( + `Roo Debug CloseLoop: Error closing original tab reference for "${tab.label}":`, + fallbackCloseError, + ) + } + } + } + + /** + * Closes all tabs that were opened by Roo based on the current settings. + * @param settings The diff settings to use for determining which tabs to close + */ + public async closeAllRooOpenedViews(settings: DiffSettings): Promise { + const closeOps = vscode.window.tabGroups.all + .flatMap((tg) => tg.tabs) + .filter((tab) => this.tabToCloseFilter(tab, settings)) + .map((tab) => this.closeTab(tab)) + + await Promise.all(closeOps) + } + + /** + * Finds the VS Code tab for a given file path. + * @param absolutePath The absolute path to the file + * @returns The tab if found, undefined otherwise + */ + private findTabForFile(absolutePath: string): vscode.Tab | undefined { + return vscode.window.tabGroups.all + .flatMap((group) => group.tabs) + .find( + (tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath), + ) + } + + /** + * Safely shows a text document with error handling. + * @param params Parameters for showing the document + * @returns The text editor or null if failed + */ + private async showTextDocumentSafe({ + uri, + textDocument, + options, + }: { + uri?: vscode.Uri + textDocument?: vscode.TextDocument + options?: vscode.TextDocumentShowOptions + }): Promise { + // If the uri is already open, we want to focus it + if (uri) { + const editor = await vscode.window.showTextDocument(uri, options) + return editor + } + // If the textDocument is already open, we want to focus it + if (textDocument) { + const editor = await vscode.window.showTextDocument(textDocument, options) + return editor + } + // If the textDocument is not open and not able to be opened, we just return null + return null + } +} diff --git a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts index ad1950345b..33b95a8b52 100644 --- a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts +++ b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts @@ -28,6 +28,14 @@ vi.mock("vscode", () => ({ fs: { stat: vi.fn(), }, + // mock vscode.workspace.getConfiguration("roo-cline").get("diffViewAutoFocus", true) + getConfiguration: vi.fn(() => ({ + get: vi.fn((key: string) => { + if (key === "autoCloseRooTabs") return true + if (key === "autoCloseAllRooTabs") return false + return undefined + }), + })), }, window: { createTextEditorDecorationType: vi.fn(), @@ -36,8 +44,24 @@ vi.mock("vscode", () => ({ tabGroups: { all: [], close: vi.fn(), + onDidChangeTabs: vi.fn(() => ({ dispose: vi.fn() })), + onDidChangeTabGroups: vi.fn(() => ({ dispose: vi.fn() })), }, visibleTextEditors: [], + onDidChangeActiveTextEditor: vi.fn(), + activeTextEditor: { + document: { + uri: { fsPath: "/mock/cwd/test.md" }, + getText: vi.fn(), + lineCount: 10, + }, + selection: { + active: { line: 0, character: 0 }, + anchor: { line: 0, character: 0 }, + }, + edit: vi.fn().mockResolvedValue(true), + revealRange: vi.fn(), + }, }, commands: { executeCommand: vi.fn(), diff --git a/src/integrations/editor/__tests__/PostDiffviewBehaviorUtils.spec.ts b/src/integrations/editor/__tests__/PostDiffviewBehaviorUtils.spec.ts new file mode 100644 index 0000000000..0f38350a1f --- /dev/null +++ b/src/integrations/editor/__tests__/PostDiffviewBehaviorUtils.spec.ts @@ -0,0 +1,251 @@ +// npx vitest run src/integrations/editor/__tests__/PostDiffViewBehaviorUtils.spec.ts + +import { describe, it, expect, vi, beforeEach } from "vitest" +import * as vscode from "vscode" +import { PostDiffViewBehaviorUtils } from "../PostDiffViewBehaviorUtils" +import { DIFF_VIEW_URI_SCHEME } from "../DiffViewProvider" + +// Mock vscode +vi.mock("vscode", () => ({ + window: { + tabGroups: { + all: [], + close: vi.fn(), + }, + showTextDocument: vi.fn(), + createTextEditorDecorationType: vi.fn(), + }, + workspace: { + textDocuments: [], + }, + TabInputTextDiff: class TabInputTextDiff { + constructor( + public original?: { scheme?: string }, + public modified?: any, + ) {} + }, + TabInputText: class TabInputText { + constructor(public uri: any) {} + }, + Uri: { + parse: vi.fn(), + file: vi.fn(), + }, +})) + +// Mock path utilities +vi.mock("../../../utils/path", () => ({ + arePathsEqual: vi.fn((a: string, b: string) => a === b), +})) + +describe("PostDiffViewBehaviorUtils", () => { + let utils: PostDiffViewBehaviorUtils + let mockContext: any + + beforeEach(() => { + vi.clearAllMocks() + mockContext = { + relPath: "test.txt", + editType: "modify" as const, + documentWasOpen: false, + cwd: "/test", + rooOpenedTabs: new Set(), + preDiffActiveEditor: undefined, + autoCloseAllRooTabs: false, + } + utils = new PostDiffViewBehaviorUtils(mockContext) + }) + + describe("updateContext", () => { + it("should update context with new values", () => { + utils.updateContext({ + relPath: "new-file.txt", + editType: "create", + }) + + // Since context is private, we can test this indirectly by calling a method that uses it + expect(() => utils.handlePostDiffFocus()).not.toThrow() + }) + }) + + describe("tabToCloseFilter", () => { + it("should always close DiffView tabs opened by Roo", () => { + const mockTab = { + input: new (vscode as any).TabInputTextDiff({ scheme: DIFF_VIEW_URI_SCHEME }), + } as vscode.Tab + + const settings = { + autoFocus: true, + autoCloseRooTabs: false, + autoCloseAllRooTabs: false, + } + + const result = utils.tabToCloseFilter(mockTab, settings) + expect(result).toBe(true) + }) + + it("should not close non-Roo opened tabs", () => { + const mockTab = { + input: new (vscode as any).TabInputText({ toString: () => "file:///other.txt" }), + } as vscode.Tab + + const settings = { + autoFocus: true, + autoCloseRooTabs: false, + autoCloseAllRooTabs: false, + } + + const result = utils.tabToCloseFilter(mockTab, settings) + expect(result).toBe(false) + }) + + it("should close all Roo-opened tabs when autoCloseAllRooTabs is true", () => { + mockContext.rooOpenedTabs.add("file:///test.txt") + utils.updateContext(mockContext) + + const mockTab = { + input: new (vscode as any).TabInputText({ + scheme: "file", + fsPath: "/test/test.txt", + toString: () => "file:///test.txt", + }), + } as vscode.Tab + + const settings = { + autoFocus: true, + autoCloseRooTabs: false, + autoCloseAllRooTabs: true, + } + + // Mock Uri.parse to return the expected URI + ;(vscode.Uri.parse as any).mockReturnValue({ + scheme: "file", + fsPath: "/test/test.txt", + toString: () => "file:///test.txt", + }) + + const result = utils.tabToCloseFilter(mockTab, settings) + expect(result).toBe(true) + }) + }) + + describe("handlePostDiffFocus", () => { + it("should return early if no relPath is set", async () => { + mockContext.relPath = undefined + utils.updateContext(mockContext) + + // Should not throw and should complete quickly + await expect(utils.handlePostDiffFocus()).resolves.toBeUndefined() + }) + + it("should focus on pre-diff active tab when autoCloseAllRooTabs is true", async () => { + const mockEditor = { + document: { uri: { toString: () => "file:///test.txt" } }, + viewColumn: 1, + } + mockContext.autoCloseAllRooTabs = true + mockContext.preDiffActiveEditor = mockEditor + utils.updateContext(mockContext) + + // Mock workspace.textDocuments to include the document + ;(vscode.workspace as any).textDocuments = [mockEditor.document] + + const showTextDocumentSpy = vi.spyOn(vscode.window, "showTextDocument") + + await utils.handlePostDiffFocus() + + expect(showTextDocumentSpy).toHaveBeenCalledWith(mockEditor.document.uri, { + viewColumn: mockEditor.viewColumn, + preserveFocus: false, + preview: false, + }) + }) + }) + + describe("closeTab", () => { + it("should close a tab successfully", async () => { + const mockTab = { + input: {}, + label: "test.txt", + } as vscode.Tab + + const closeSpy = vi.spyOn(vscode.window.tabGroups, "close") + closeSpy.mockResolvedValue(true) + + // Mock tabGroups.all to return the same tab + ;(vscode.window.tabGroups as any).all = [ + { + tabs: [mockTab], + }, + ] + + await utils.closeTab(mockTab) + + expect(closeSpy).toHaveBeenCalledWith(mockTab, true) + }) + + it("should handle tab close errors gracefully", async () => { + const mockTab = { + input: {}, + label: "test.txt", + } as vscode.Tab + + const closeSpy = vi.spyOn(vscode.window.tabGroups, "close") + closeSpy.mockRejectedValue(new Error("Close failed")) + + // Mock tabGroups.all to return the same tab + ;(vscode.window.tabGroups as any).all = [ + { + tabs: [mockTab], + }, + ] + + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + + await utils.closeTab(mockTab) + + expect(consoleErrorSpy).toHaveBeenCalled() + consoleErrorSpy.mockRestore() + }) + }) + + describe("closeAllRooOpenedViews", () => { + it("should close all tabs that match the filter", async () => { + const mockTab1 = { + input: new (vscode as any).TabInputTextDiff({ scheme: DIFF_VIEW_URI_SCHEME }), + label: "diff-tab", + } as vscode.Tab + + const mockTab2 = { + input: new (vscode as any).TabInputText({ + scheme: "file", + fsPath: "/test/other.txt", + toString: () => "file:///other.txt", + }), + label: "other.txt", + } as vscode.Tab + + // Mock tabGroups.all + ;(vscode.window.tabGroups as any).all = [ + { + tabs: [mockTab1, mockTab2], + }, + ] + + const closeSpy = vi.spyOn(vscode.window.tabGroups, "close") + closeSpy.mockResolvedValue(true) + + const settings = { + autoFocus: true, + autoCloseRooTabs: false, + autoCloseAllRooTabs: false, + } + + await utils.closeAllRooOpenedViews(settings) + + // Should only close the diff tab + expect(closeSpy).toHaveBeenCalledTimes(1) + expect(closeSpy).toHaveBeenCalledWith(mockTab1, true) + }) + }) +}) diff --git a/src/package.json b/src/package.json index bc55107fc8..fede11487b 100644 --- a/src/package.json +++ b/src/package.json @@ -366,6 +366,16 @@ "type": "string", "default": "", "description": "%settings.autoImportSettingsPath.description%" + }, + "roo-cline.autoCloseRooTabs": { + "type": "boolean", + "default": false, + "description": "%roo-cline.autoCloseRooTabs.description%" + }, + "roo-cline.autoCloseAllRooTabs": { + "type": "boolean", + "default": false, + "description": "%roo-cline.autoCloseAllRooTabs.description%" } } } diff --git a/src/package.nls.json b/src/package.nls.json index 2c8908fadb..809fe7f78a 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -33,5 +33,7 @@ "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.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.", + "roo-cline.autoCloseRooTabs.description": "When enabled, Roo will automatically close tabs for files that were newly created or were not already open before Roo modified them. Tabs for files that were already open will remain open.", + "roo-cline.autoCloseAllRooTabs.description": "When enabled (and 'Automatically close newly created or previously unopened Roo tabs' is also enabled), Roo will close all tabs it interacted with during a file modification task, including those that were already open before the task started." } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 833c51336b..7b69a66b3d 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -211,6 +211,8 @@ export type ExtensionState = Pick< | "terminalZdotdir" | "terminalCompressProgressBar" | "diffEnabled" + | "autoCloseRooTabs" + | "autoCloseAllRooTabs" | "fuzzyMatchThreshold" // | "experiments" // Optional in GlobalSettings, required here. | "language" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d5dc3f8c28..cd3ae5b0ca 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -93,6 +93,8 @@ export interface WebviewMessage { | "ttsSpeed" | "soundVolume" | "diffEnabled" + | "autoCloseRooTabs" + | "autoCloseAllRooTabs" | "enableCheckpoints" | "browserViewportSize" | "screenshotQuality" diff --git a/webview-ui/src/components/settings/FileEditingOptions.tsx b/webview-ui/src/components/settings/FileEditingOptions.tsx new file mode 100644 index 0000000000..a51accc2b8 --- /dev/null +++ b/webview-ui/src/components/settings/FileEditingOptions.tsx @@ -0,0 +1,129 @@ +import React, { useCallback } from "react" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { SectionHeader } from "@/components/settings/SectionHeader" +import { Section } from "@/components/settings/Section" +import { FileDiff } from "lucide-react" + +type FileEditingOptionsField = + | "autoCloseRooTabs" + | "autoCloseAllRooTabs" + +interface FileEditingOptionsProps { + autoCloseRooTabs?: boolean + autoCloseAllRooTabs?: boolean + onChange: (field: FileEditingOptionsField, value: any) => void +} + +interface DiffCheckAutoCloseControlProps { + autoCloseRooTabs: boolean + disabled: boolean + onChange: (e: any) => void +} + +interface DiffCheckAutoCloseAllControlProps { + autoCloseAllRooTabs: boolean + disabled: boolean + onChange: (e: any) => void +} + +/** + * Control for auto-closing Roo tabs setting + */ +const DiffViewAutoCloseControl: React.FC = ({ + autoCloseRooTabs, + disabled, + onChange, +}) => { + const { t } = useAppTranslation() + return ( +
+ + {t("settings:advanced.diff.autoClose.label")} + +
+ {t("settings:advanced.diff.autoClose.description")} +
+
+ ) +} + +/** + * Control for auto-closing all Roo tabs setting + */ +const DiffViewAutoCloseAllControl: React.FC = ({ + autoCloseAllRooTabs, + disabled, + onChange, +}) => { + const { t } = useAppTranslation() + return ( +
+ + {t("settings:advanced.diff.autoCloseAll.label")} + +
+ {t("settings:advanced.diff.autoCloseAll.description")} +
+
+ ) +} + +/** + * File editing options control component with mutual exclusivity logic + */ +export const FileEditingOptions: React.FC = ({ + autoCloseRooTabs = false, + autoCloseAllRooTabs = false, + onChange, +}) => { + const { t } = useAppTranslation() + + const handleAutoCloseRooTabsChange = useCallback( + (e: any) => { + onChange("autoCloseRooTabs", e.target.checked) + // If autoCloseRooTabs is unchecked, also uncheck autoCloseAllRooTabs + if (!e.target.checked) { + onChange("autoCloseAllRooTabs", false) + } + }, + [onChange], + ) + + const handleAutoCloseAllRooTabsChange = useCallback( + (e: any) => { + onChange("autoCloseAllRooTabs", e.target.checked) + }, + [onChange], + ) + + return ( +
+ + {/* Diff settings section */} + +
+ +
{t("settings:sections.diffSettings")}
+
+
+ +
+ {/* Diff settings section */} +
+ + +
+
+ +
+ ) +} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index cf9e779cbd..97dea886c0 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -18,6 +18,7 @@ import { Database, SquareTerminal, FlaskConical, + Pencil, AlertTriangle, Globe, Info, @@ -65,6 +66,7 @@ import { LanguageSettings } from "./LanguageSettings" import { About } from "./About" import { Section } from "./Section" import PromptsSettings from "./PromptsSettings" +import { FileEditingOptions } from "./FileEditingOptions" import { cn } from "@/lib/utils" export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden" @@ -80,6 +82,7 @@ export interface SettingsViewRef { const sectionNames = [ "providers", + "fileEditing", "autoApprove", "browser", "checkpoints", @@ -176,6 +179,8 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowFollowupQuestions, alwaysAllowUpdateTodoList, followupAutoApproveTimeoutMs, + autoCloseRooTabs, + autoCloseAllRooTabs, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -285,6 +290,8 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "ttsSpeed", value: ttsSpeed }) vscode.postMessage({ type: "soundVolume", value: soundVolume }) vscode.postMessage({ type: "diffEnabled", bool: diffEnabled }) + vscode.postMessage({ type: "autoCloseRooTabs", bool: autoCloseRooTabs }) + vscode.postMessage({ type: "autoCloseAllRooTabs", bool: autoCloseAllRooTabs }) vscode.postMessage({ type: "enableCheckpoints", bool: enableCheckpoints }) vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize }) vscode.postMessage({ type: "remoteBrowserHost", text: remoteBrowserHost }) @@ -393,6 +400,7 @@ const SettingsView = forwardRef(({ onDone, t const sections: { id: SectionName; icon: LucideIcon }[] = useMemo( () => [ { id: "providers", icon: Webhook }, + { id: "fileEditing", icon: Pencil }, { id: "autoApprove", icon: CheckCheck }, { id: "browser", icon: SquareMousePointer }, { id: "checkpoints", icon: GitBranch }, @@ -588,6 +596,25 @@ const SettingsView = forwardRef(({ onDone, t )} + {/* File Editing Section */} + {activeTab === "fileEditing" && ( +
+ +
+ +
{t("settings:sections.fileEditing")}
+
+
+ + +
+ )} + + {/* Auto-Approve Section */} {activeTab === "autoApprove" && ( key) +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ t: mockT }), +})) + +// Mock VSCode components +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeCheckbox: ({ children, checked, disabled, onChange, ...props }: any) => ( + + ), +})) + +// Mock UI components +vi.mock("@/components/ui", () => ({ + Slider: ({ value, onValueChange, disabled, ...props }: any) => ( + onValueChange([parseFloat(e.target.value)])} + disabled={disabled} + data-testid="slider" + {...props} + /> + ), +})) + +describe("FileEditingOptions", () => { + const defaultProps = { + autoCloseRooTabs: false, + autoCloseAllRooTabs: false, + onChange: vi.fn(), + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + describe("Diff settings section", () => { + + it("should show diff sub-controls when diff is enabled and file-based editing is disabled", () => { + render() + + expect(mockT).toHaveBeenCalledWith("settings:diffSettings.description") + expect(mockT).toHaveBeenCalledWith("settings:advanced.diff.autoClose.label") + expect(mockT).toHaveBeenCalledWith("settings:advanced.diff.autoCloseAll.label") + }) + }) + + describe("Change handlers", () => { + + it("should call onChange for autoCloseRooTabs and uncheck autoCloseAllRooTabs when unchecked", () => { + const onChange = vi.fn() + render( + , + ) + + const checkbox = screen.getByRole("checkbox", { name: /autoClose.label/ }) + fireEvent.click(checkbox) + + expect(onChange).toHaveBeenCalledWith("autoCloseRooTabs", false) + expect(onChange).toHaveBeenCalledWith("autoCloseAllRooTabs", false) + }) + }) +}) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 6c70c8940d..1110341ac9 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -80,6 +80,8 @@ export interface ExtensionStateContextType extends ExtensionState { setTtsEnabled: (value: boolean) => void setTtsSpeed: (value: number) => void setDiffEnabled: (value: boolean) => void + setAutoCloseRooTabs: (value: boolean) => void + setAutoCloseAllRooTabs: (value: boolean) => void setEnableCheckpoints: (value: boolean) => void setBrowserViewportSize: (value: string) => void setFuzzyMatchThreshold: (value: number) => void @@ -169,6 +171,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode ttsEnabled: false, ttsSpeed: 1.0, diffEnabled: false, + autoCloseRooTabs: false, + autoCloseAllRooTabs: false, enableCheckpoints: true, fuzzyMatchThreshold: 1.0, language: "en", // Default language code @@ -402,6 +406,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setTtsEnabled: (value) => setState((prevState) => ({ ...prevState, ttsEnabled: value })), setTtsSpeed: (value) => setState((prevState) => ({ ...prevState, ttsSpeed: value })), setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })), + setAutoCloseRooTabs: (value) => { + setState((prevState) => ({ + ...prevState, + autoCloseRooTabs: value, + })) + }, + setAutoCloseAllRooTabs: (value) => setState((prevState) => ({ ...prevState, autoCloseAllRooTabs: value })), setEnableCheckpoints: (value) => setState((prevState) => ({ ...prevState, enableCheckpoints: value })), setBrowserViewportSize: (value: string) => setState((prevState) => ({ ...prevState, browserViewportSize: value })), diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 0dd08afb29..679a40138f 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Precisió de coincidència", "description": "Aquest control lliscant controla amb quina precisió han de coincidir les seccions de codi en aplicar diffs. Valors més baixos permeten coincidències més flexibles però augmenten el risc de reemplaçaments incorrectes. Utilitzeu valors per sota del 100% amb extrema precaució." + }, + "autoClose": { + "label": "Tanca automàticament les pestanyes de Roo", + "description": "Quan està activat, Roo tancarà automàticament les pestanyes que hagi obert (com les vistes de diferències i els fitxers oberts) quan una tasca que impliqui modificació de fitxers es completi o es reverteixi." + }, + "autoCloseAll": { + "label": "Tancar automàticament totes les pestanyes tocades per Roo", + "description": "Quan està activat (i la configuració anterior també està activada), Roo tancarà totes les pestanyes amb les quals va interactuar durant una tasca de modificació de fitxers, incloses aquelles que ja estaven obertes abans que comencés la tasca." } } }, diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 923475d3fd..0ef432cf93 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Übereinstimmungspräzision", "description": "Dieser Schieberegler steuert, wie genau Codeabschnitte bei der Anwendung von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen eine flexiblere Übereinstimmung, erhöhen aber das Risiko falscher Ersetzungen. Verwenden Sie Werte unter 100 % mit äußerster Vorsicht." + }, + "autoClose": { + "label": "Roo-Tabs automatisch schließen", + "description": "Wenn aktiviert, schließt Roo automatisch alle Tabs, die es selbst geöffnet hat (wie Diff-Ansichten und geöffnete Dateien), wenn eine Aufgabe mit Dateiänderungen abgeschlossen oder zurückgesetzt wird." + }, + "autoCloseAll": { + "label": "Alle von Roo berührten Tabs automatisch schließen", + "description": "Wenn aktiviert (und die obige Einstellung ebenfalls aktiviert ist), schließt Roo alle Tabs, mit denen es während einer Dateiänderungsaufgabe interagiert hat, einschließlich derjenigen, die bereits vor Beginn der Aufgabe geöffnet waren." } } }, diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 25428cfb16..6b4f151c55 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Match precision", "description": "This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution." + }, + "autoClose": { + "label": "Automatically close newly created or previously unopened Roo tabs", + "description": "When enabled, Roo will automatically close tabs for files that were newly created or were not already open before Roo modified them. Tabs for files that were already open will remain open." + }, + "autoCloseAll": { + "label": "Automatically close all Roo-touched tabs", + "description": "When enabled (and the above setting is also enabled), Roo will close all tabs it interacted with during a file modification task, including those that were already open before the task started." } } }, diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 7f6af9bd72..4788029af8 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Precisión de coincidencia", "description": "Este control deslizante controla cuán precisamente deben coincidir las secciones de código al aplicar diffs. Valores más bajos permiten coincidencias más flexibles pero aumentan el riesgo de reemplazos incorrectos. Use valores por debajo del 100% con extrema precaución." + }, + "autoClose": { + "label": "Cerrar automáticamente las pestañas de Roo", + "description": "Cuando está habilitado, Roo cerrará automáticamente las pestañas que haya abierto (como vistas de diferencias y archivos abiertos) cuando una tarea que involucre modificación de archivos se complete o se revierta." + }, + "autoCloseAll": { + "label": "Cerrar automáticamente todas las pestañas tocadas por Roo", + "description": "Cuando está habilitado (y la configuración anterior también está habilitada), Roo cerrará todas las pestañas con las que interactuó durante una tarea de modificación de archivos, incluidas aquellas que ya estaban abiertas antes de que comenzara la tarea." } } }, diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 5986a5bf17..995b4c80f0 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Précision de correspondance", "description": "Ce curseur contrôle la précision avec laquelle les sections de code doivent correspondre lors de l'application des diffs. Des valeurs plus basses permettent des correspondances plus flexibles mais augmentent le risque de remplacements incorrects. Utilisez des valeurs inférieures à 100 % avec une extrême prudence." + }, + "autoClose": { + "label": "Fermer automatiquement les onglets Roo", + "description": "Lorsqu'activé, Roo fermera automatiquement les onglets qu'il a ouverts (vues de différences, fichiers ouverts) lorsqu'une tâche impliquant une modification de fichiers est terminée ou annulée." + }, + "autoCloseAll": { + "label": "Fermer automatiquement tous les onglets touchés par Roo", + "description": "Lorsqu'activé (et que le paramètre ci-dessus est également activé), Roo fermera tous les onglets avec lesquels il a interagi lors d'une tâche de modification de fichier, y compris ceux qui étaient déjà ouverts avant le début de la tâche." } } }, diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0a99064588..47a0c1b353 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "मिलान सटीकता", "description": "यह स्लाइडर नियंत्रित करता है कि diffs लागू करते समय कोड अनुभागों को कितनी सटीकता से मेल खाना चाहिए। निम्न मान अधिक लचीले मिलान की अनुमति देते हैं लेकिन गलत प्रतिस्थापन का जोखिम बढ़ाते हैं। 100% से नीचे के मानों का उपयोग अत्यधिक सावधानी के साथ करें।" + }, + "autoClose": { + "label": "Roo टैब स्वचालित रूप से बंद करें", + "description": "जब सक्षम किया जाता है, तो Roo उन सभी टैब को स्वचालित रूप से बंद कर देगा जिन्हें इसने खोला है (जैसे डिफ व्यू और खुली फ़ाइलें) जब फ़ाइल संशोधन वाला कार्य पूरा हो जाता है या वापस लिया जाता है।" + }, + "autoCloseAll": { + "label": "Roo द्वारा छुए गए सभी टैब स्वचालित रूप से बंद करें", + "description": "जब सक्षम किया जाता है (और उपरोक्त सेटिंग भी सक्षम है), तो Roo उन सभी टैब को स्वचालित रूप से बंद कर देगा जिनके साथ इसने फ़ाइल संशोधन कार्य के दौरान इंटरैक्ट किया था, जिसमें वे टैब भी शामिल हैं जो कार्य शुरू होने से पहले ही खुले थे।" } } }, diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 9ae5dd846c..6de531c92a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -560,6 +560,14 @@ "matchPrecision": { "label": "Presisi pencocokan", "description": "Slider ini mengontrol seberapa tepat bagian kode harus cocok saat menerapkan diff. Nilai yang lebih rendah memungkinkan pencocokan yang lebih fleksibel tetapi meningkatkan risiko penggantian yang salah. Gunakan nilai di bawah 100% dengan sangat hati-hati." + }, + "autoClose": { + "label": "Otomatis tutup tab Roo yang baru dibuat atau sebelumnya tidak terbuka", + "description": "Ketika diaktifkan, Roo akan otomatis menutup tab untuk file yang baru dibuat atau tidak terbuka sebelum Roo memodifikasinya. Tab untuk file yang sudah terbuka akan tetap terbuka." + }, + "autoCloseAll": { + "label": "Otomatis tutup semua tab yang disentuh Roo", + "description": "Ketika diaktifkan (dan pengaturan di atas juga diaktifkan), Roo akan menutup semua tab yang berinteraksi dengannya selama tugas modifikasi file, termasuk yang sudah terbuka sebelum tugas dimulai." } } }, diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index a2897496ec..51990a2fc9 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Precisione corrispondenza", "description": "Questo cursore controlla quanto precisamente le sezioni di codice devono corrispondere quando si applicano i diff. Valori più bassi consentono corrispondenze più flessibili ma aumentano il rischio di sostituzioni errate. Usa valori inferiori al 100% con estrema cautela." + }, + "autoClose": { + "label": "Chiudi automaticamente le schede Roo", + "description": "Quando abilitato, Roo chiuderà automaticamente le schede che ha aperto (come viste diff e file aperti) quando un'attività che coinvolge la modifica di file viene completata o annullata." + }, + "autoCloseAll": { + "label": "Chiudi automaticamente tutte le schede toccate da Roo", + "description": "Quando abilitato (e anche l'impostazione precedente è abilitata), Roo chiuderà tutte le schede con cui ha interagito durante un'attività di modifica dei file, incluse quelle che erano già aperte prima dell'inizio dell'attività." } } }, diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index bda4182851..370f2039d3 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "マッチ精度", "description": "このスライダーは、diffを適用する際にコードセクションがどれだけ正確に一致する必要があるかを制御します。低い値はより柔軟なマッチングを可能にしますが、誤った置換のリスクが高まります。100%未満の値は細心の注意を払って使用してください。" + }, + "autoClose": { + "label": "Rooのタブを自動的に閉じる", + "description": "有効にすると、ファイルの変更を含むタスクが完了または取り消された時に、Rooが開いたタブ(差分ビューや開いたファイルなど)を自動的に閉じます。" + }, + "autoCloseAll": { + "label": "Rooが触れたすべてのタブを自動的に閉じる", + "description": "有効にすると(そして上記の設定も有効になっている場合)、Rooはファイル変更タスク中に操作したすべてのタブ(タスク開始前にすでに開いていたタブを含む)を自動的に閉じます。" } } }, diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 5053ed62f3..4dc608b7c9 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "일치 정확도", "description": "이 슬라이더는 diff를 적용할 때 코드 섹션이 얼마나 정확하게 일치해야 하는지 제어합니다. 낮은 값은 더 유연한 일치를 허용하지만 잘못된 교체 위험이 증가합니다. 100% 미만의 값은 극도로 주의해서 사용하세요." + }, + "autoClose": { + "label": "Roo 탭 자동 닫기", + "description": "활성화 시 Roo는 파일 수정이 포함된 작업이 완료되거나 취소될 때 자동으로 열었던 탭(차이점 보기, 열린 파일 등)을 닫습니다." + }, + "autoCloseAll": { + "label": "Roo가 수정한 모든 탭 자동 닫기", + "description": "활성화 시 (그리고 위의 설정도 활성화된 경우), Roo는 파일 수정 작업 중에 상호 작용한 모든 탭(작업 시작 전에 이미 열려 있던 탭 포함)을 자동으로 닫습니다." } } }, diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index ab12e1931c..90f6275468 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Matchnauwkeurigheid", "description": "Deze schuifregelaar bepaalt hoe nauwkeurig codeblokken moeten overeenkomen bij het toepassen van diffs. Lagere waarden laten flexibelere matching toe maar verhogen het risico op verkeerde vervangingen. Gebruik waarden onder 100% met uiterste voorzichtigheid." + }, + "autoClose": { + "label": "Door Roo geopende tabbladen automatisch sluiten", + "description": "Indien ingeschakeld, sluit Roo automatisch alle tabbladen die het zelf heeft geopend (zoals diff-weergaven, geopende bestanden) wanneer een taak met bestandswijzigingen wordt voltooid of teruggedraaid." + }, + "autoCloseAll": { + "label": "Sluit automatisch alle tabbladen die door Roo zijn aangeraakt", + "description": "Indien ingeschakeld (en de bovenstaande instelling is ook ingeschakeld), sluit Roo automatisch alle tabbladen waarmee het heeft geïnterageerd tijdens een taak voor bestandsaanpassing, inclusief de tabbladen die al open waren voordat de taak begon." } } }, diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 266c668606..7c04d47a6a 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Precyzja dopasowania", "description": "Ten suwak kontroluje, jak dokładnie sekcje kodu muszą pasować podczas stosowania różnic. Niższe wartości umożliwiają bardziej elastyczne dopasowywanie, ale zwiększają ryzyko nieprawidłowych zamian. Używaj wartości poniżej 100% z najwyższą ostrożnością." + }, + "autoClose": { + "label": "Automatycznie zamykaj karty Roo", + "description": "Gdy włączone, Roo automatycznie zamknie karty, które otworzyło (takie jak widoki różnic i otwarte pliki), gdy zadanie obejmujące modyfikację plików zostanie zakończone lub cofnięte." + }, + "autoCloseAll": { + "label": "Automatycznie zamykaj wszystkie karty dotknięte przez Roo", + "description": "Gdy włączone (i powyższe ustawienie jest również włączone), Roo automatycznie zamknie wszystkie karty, z którymi wchodził w interakcję podczas zadania modyfikacji plików, w tym te, które były już otwarte przed rozpoczęciem zadania." } } }, diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index e9825ae749..40c8789c2d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Precisão de correspondência", "description": "Este controle deslizante controla quão precisamente as seções de código devem corresponder ao aplicar diffs. Valores mais baixos permitem correspondências mais flexíveis, mas aumentam o risco de substituições incorretas. Use valores abaixo de 100% com extrema cautela." + }, + "autoClose": { + "label": "Fechar automaticamente as abas do Roo", + "description": "Quando ativado, o Roo fechará automaticamente as abas que abriu (como visualizações de diferenças e arquivos abertos) quando uma tarefa que envolve modificação de arquivos é concluída ou revertida." + }, + "autoCloseAll": { + "label": "Fechar automaticamente todas as abas tocadas pelo Roo", + "description": "Quando ativado (e a configuração acima também está ativada), o Roo fechará automaticamente todas as abas com as quais interagiu durante uma tarefa de modificação de arquivo, incluindo aquelas que já estavam abertas antes do início da tarefa." } } }, diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 1b74b2253f..c9714252c9 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Точность совпадения", "description": "Этот ползунок управляет точностью совпадения секций кода при применении диффов. Меньшие значения позволяют более гибкое совпадение, но увеличивают риск неверной замены. Используйте значения ниже 100% с осторожностью." + }, + "autoClose": { + "label": "Автоматически закрывать вкладки Roo", + "description": "Если включено, Roo будет автоматически закрывать открытые им вкладки (такие как просмотр изменений и открытые файлы), когда задача, включающая модификацию файлов, завершается или отменяется." + }, + "autoCloseAll": { + "label": "Автоматически закрывать все вкладки, затронутые Roo", + "description": "Если включено (и предыдущая настройка также включена), Roo будет автоматически закрывать все вкладки, с которыми он взаимодействовал во время задачи модификации файлов, включая те, которые были открыты до начала задачи." } } }, diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 49d1803ddf..2afe30f3ec 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Eşleşme hassasiyeti", "description": "Bu kaydırıcı, diff'ler uygulanırken kod bölümlerinin ne kadar hassas bir şekilde eşleşmesi gerektiğini kontrol eder. Daha düşük değerler daha esnek eşleşmeye izin verir ancak yanlış değiştirme riskini artırır. %100'ün altındaki değerleri son derece dikkatli kullanın." + }, + "autoClose": { + "label": "Roo'nun açtığı sekmeleri otomatik kapat", + "description": "Etkinleştirildiğinde, Roo dosya değişikliği içeren bir görev tamamlandığında veya geri alındığında açtığı sekmeleri (ör. diff görünümleri, açılan dosyalar) otomatik olarak kapatacaktır." + }, + "autoCloseAll": { + "label": "Roo'nun dokunduğu tüm sekmeleri otomatik kapat", + "description": "Etkinleştirildiğinde (ve yukarıdaki ayar da etkinleştirildiğinde), Roo bir dosya değiştirme görevi sırasında etkileşimde bulunduğu tüm sekmeleri (görev başlamadan önce zaten açık olanlar dahil) otomatik olarak kapatır." } } }, diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index c12b5778a2..ee661a2581 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "Độ chính xác khớp", "description": "Thanh trượt này kiểm soát mức độ chính xác các phần mã phải khớp khi áp dụng diff. Giá trị thấp hơn cho phép khớp linh hoạt hơn nhưng tăng nguy cơ thay thế không chính xác. Sử dụng giá trị dưới 100% với sự thận trọng cao." + }, + "autoClose": { + "label": "Tự động đóng các tab Roo", + "description": "Khi bật, Roo sẽ tự động đóng các tab nó đã mở (như chế độ xem diff, các tệp đã mở) khi một tác vụ liên quan đến sửa đổi tệp hoàn thành hoặc được hoàn tác." + }, + "autoCloseAll": { + "label": "Tự động đóng tất cả các tab mà Roo đã chạm vào", + "description": "Khi được bật (và cài đặt ở trên cũng được bật), Roo sẽ tự động đóng tất cả các tab mà nó đã tương tác trong quá trình sửa đổi tệp, bao gồm cả những tab đã mở trước khi tác vụ bắt đầu." } } }, diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index c75578067d..4dca58a092 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "匹配精度", "description": "控制代码匹配的精确程度。数值越低匹配越宽松(容错率高但风险大),建议保持100%以确保安全。" + }, + "autoClose": { + "label": "自动关闭Roo打开的标签页", + "description": "启用后,Roo会在涉及文件修改的任务完成或回退时自动关闭它自己打开的标签页(如差异视图、打开的文件)。" + }, + "autoCloseAll": { + "label": "自动关闭所有Roo操作过的标签页", + "description": "启用后(且上述设置也已启用),Roo将在涉及文件修改的任务完成或回滚时,自动关闭所有在此任务中交互过的标签页,包括任务开始前已打开的标签页。" } } }, diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index dec9e936b2..ce0c55723f 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -556,6 +556,14 @@ "matchPrecision": { "label": "比對精確度", "description": "此滑桿控制套用差異時程式碼區段的比對精確度。較低的數值允許更彈性的比對,但也會增加錯誤取代的風險。使用低於 100% 的數值時請特別謹慎。" + }, + "autoClose": { + "label": "自動關閉 Roo 開啟的分頁", + "description": "啟用後,Roo 會在涉及檔案修改的任務完成或復原時,自動關閉它開啟的分頁(如差異檢視、開啟的檔案)。" + }, + "autoCloseAll": { + "label": "自動關閉所有 Roo 操作過的分頁", + "description": "啟用後(且上述設定也已啟用),Roo 將在涉及檔案修改的任務完成或復原時,自動關閉所有在此任務中互動過的分頁,包括任務開始前已開啟的分頁。" } } },