Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/small-snakes-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": patch
---

Added an editable setting to allow users to control the closing of diff tab and corresponding file.
2 changes: 2 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),

Expand Down
2 changes: 2 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,8 @@ export class Task extends EventEmitter<ClineEvents> {
private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise<void> {
// 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
Expand Down
6 changes: 6 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,8 @@ export class ClineProvider
ttsEnabled,
ttsSpeed,
diffEnabled,
autoCloseRooTabs,
autoCloseAllRooTabs,
enableCheckpoints,
taskHistory,
soundVolume,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
20 changes: 20 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
196 changes: 141 additions & 55 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,15 +41,88 @@ export class DiffViewProvider {
private activeLineController?: DecorationController
private streamedLines: string[] = []
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
private rooOpenedTabs: Set<string> = 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<boolean>("autoCloseRooTabs", false)
const autoCloseAllRooTabs = config.get<boolean>("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<void> {
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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<void> {
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<vscode.TextEditor> {
if (!this.relPath) {
throw new Error(
Expand Down Expand Up @@ -580,7 +658,13 @@ export class DiffViewProvider {
}

async reset(): Promise<void> {
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
Expand All @@ -591,5 +675,7 @@ export class DiffViewProvider {
this.activeLineController = undefined
this.streamedLines = []
this.preDiagnostics = []
this.rooOpenedTabs.clear()

}
}
Loading
Loading