Skip to content

Commit 34d299c

Browse files
committed
feat(autoClose): Add auto-close options for Roo tabs in settings
1 parent e84dd0a commit 34d299c

35 files changed

+1168
-56
lines changed

.changeset/small-snakes-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Added an editable setting to allow users to control the closing of diff tab and corresponding file.

packages/types/src/global-settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ export const globalSettingsSchema = z.object({
8787

8888
rateLimitSeconds: z.number().optional(),
8989
diffEnabled: z.boolean().optional(),
90+
autoCloseRooTabs: z.boolean().optional(),
91+
autoCloseAllRooTabs: z.boolean().optional(),
9092
fuzzyMatchThreshold: z.number().optional(),
9193
experiments: experimentsSchema.optional(),
9294

src/core/task/Task.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,8 @@ export class Task extends EventEmitter<ClineEvents> {
11181118
private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise<void> {
11191119
// Kicks off the checkpoints initialization process in the background.
11201120
getCheckpointService(this)
1121+
// Initialize the diff view provider for this task.
1122+
this.diffViewProvider.initialize()
11211123

11221124
let nextUserContent = userContent
11231125
let includeFileDetails = true

src/core/webview/ClineProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,8 @@ export class ClineProvider
13801380
ttsEnabled,
13811381
ttsSpeed,
13821382
diffEnabled,
1383+
autoCloseRooTabs,
1384+
autoCloseAllRooTabs,
13831385
enableCheckpoints,
13841386
taskHistory,
13851387
soundVolume,
@@ -1476,6 +1478,8 @@ export class ClineProvider
14761478
ttsEnabled: ttsEnabled ?? false,
14771479
ttsSpeed: ttsSpeed ?? 1.0,
14781480
diffEnabled: diffEnabled ?? true,
1481+
autoCloseRooTabs: autoCloseRooTabs ?? false,
1482+
autoCloseAllRooTabs: autoCloseAllRooTabs ?? false,
14791483
enableCheckpoints: enableCheckpoints ?? true,
14801484
shouldShowAnnouncement:
14811485
telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId,
@@ -1642,6 +1646,8 @@ export class ClineProvider
16421646
ttsEnabled: stateValues.ttsEnabled ?? false,
16431647
ttsSpeed: stateValues.ttsSpeed ?? 1.0,
16441648
diffEnabled: stateValues.diffEnabled ?? true,
1649+
autoCloseRooTabs: stateValues.autoCloseRooTabs ?? false,
1650+
autoCloseAllRooTabs: stateValues.autoCloseAllRooTabs ?? false,
16451651
enableCheckpoints: stateValues.enableCheckpoints ?? true,
16461652
soundVolume: stateValues.soundVolume,
16471653
browserViewportSize: stateValues.browserViewportSize ?? "900x600",

src/core/webview/webviewMessageHandler.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,26 @@ export const webviewMessageHandler = async (
251251
}
252252

253253
switch (message.type) {
254+
case "autoCloseRooTabs":
255+
const autoCloseRooTabs = message.bool ?? false
256+
await provider.context.globalState.update("autoCloseRooTabs", autoCloseRooTabs)
257+
// Also update workspace settings
258+
await vscode.workspace
259+
.getConfiguration("roo-cline")
260+
.update("autoCloseRooTabs", autoCloseRooTabs, vscode.ConfigurationTarget.Global)
261+
await updateGlobalState("autoCloseRooTabs", autoCloseRooTabs)
262+
await provider.postStateToWebview()
263+
break
264+
case "autoCloseAllRooTabs":
265+
const autoCloseAllRooTabs = message.bool ?? false
266+
await provider.context.globalState.update("autoCloseAllRooTabs", autoCloseAllRooTabs)
267+
// Also update workspace settings
268+
await vscode.workspace
269+
.getConfiguration("roo-cline")
270+
.update("autoCloseAllRooTabs", autoCloseAllRooTabs, vscode.ConfigurationTarget.Global)
271+
await updateGlobalState("autoCloseAllRooTabs", autoCloseAllRooTabs)
272+
await provider.postStateToWebview()
273+
break
254274
case "webviewDidLaunch":
255275
// Load custom modes first
256276
const customModes = await provider.customModesManager.getCustomModes()

src/integrations/editor/DiffViewProvider.ts

Lines changed: 141 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ import { ClineSayTool } from "../../shared/ExtensionMessage"
1313
import { Task } from "../../core/task/Task"
1414

1515
import { DecorationController } from "./DecorationController"
16+
import { PostDiffViewBehaviorUtils } from "./PostDiffViewBehaviorUtils"
17+
import { TextDocument, TextDocumentShowOptions } from "vscode"
1618

1719
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
1820
export const DIFF_VIEW_LABEL_CHANGES = "Original ↔ Roo's Changes"
1921

22+
interface DiffSettings {
23+
autoCloseRooTabs: boolean
24+
autoCloseAllRooTabs: boolean
25+
}
26+
2027
// TODO: https://github.com/cline/cline/pull/3354
2128
export class DiffViewProvider {
2229
// Properties to store the results of saveChanges
@@ -34,15 +41,88 @@ export class DiffViewProvider {
3441
private activeLineController?: DecorationController
3542
private streamedLines: string[] = []
3643
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
44+
private rooOpenedTabs: Set<string> = new Set()
45+
private autoCloseAllRooTabs: boolean = false // Added new setting
46+
private preDiffActiveEditor?: vscode.TextEditor // Store active editor before diff operation
47+
private postDiffBehaviorUtils: PostDiffViewBehaviorUtils
48+
49+
constructor(private cwd: string) {
50+
// Initialize PostDiffviewBehaviorUtils with initial context
51+
this.postDiffBehaviorUtils = new PostDiffViewBehaviorUtils({
52+
relPath: this.relPath,
53+
editType: this.editType,
54+
documentWasOpen: this.documentWasOpen,
55+
cwd: this.cwd,
56+
rooOpenedTabs: this.rooOpenedTabs,
57+
preDiffActiveEditor: this.preDiffActiveEditor,
58+
autoCloseAllRooTabs: this.autoCloseAllRooTabs,
59+
})
60+
}
3761

38-
constructor(private cwd: string) {}
62+
public initialize() {
63+
const settings = this._readDiffSettings()
64+
this.autoCloseAllRooTabs = settings.autoCloseAllRooTabs
65+
// Track currently visible editors and active editor for focus restoration and tab cleanup
66+
this.rooOpenedTabs.clear()
67+
68+
// Update PostDiffviewBehaviorUtils context with latest values
69+
this.postDiffBehaviorUtils.updateContext({
70+
autoCloseAllRooTabs: this.autoCloseAllRooTabs,
71+
})
72+
}
73+
74+
private _readDiffSettings(): DiffSettings {
75+
const config = vscode.workspace.getConfiguration("roo-cline")
76+
const autoCloseRooTabs = config.get<boolean>("autoCloseRooTabs", false)
77+
const autoCloseAllRooTabs = config.get<boolean>("autoCloseAllRooTabs", false)
78+
return { autoCloseRooTabs, autoCloseAllRooTabs }
79+
}
80+
81+
private async showTextDocumentSafe({
82+
uri,
83+
textDocument,
84+
options,
85+
}: {
86+
uri?: vscode.Uri
87+
textDocument?: TextDocument
88+
options?: TextDocumentShowOptions
89+
}) {
90+
// If the uri is already open, we want to focus it
91+
if (uri) {
92+
const editor = await vscode.window.showTextDocument(uri, options)
93+
return editor
94+
}
95+
// If the textDocument is already open, we want to focus it
96+
if (textDocument) {
97+
const editor = await vscode.window.showTextDocument(textDocument, options)
98+
return editor
99+
}
100+
// If the textDocument is not open and not able to be opened, we just reset the suppressInteractionFlag
101+
return null
102+
}
39103

40104
async open(relPath: string): Promise<void> {
105+
this.preDiffActiveEditor = vscode.window.activeTextEditor
106+
41107
this.relPath = relPath
42108
const fileExists = this.editType === "modify"
43109
const absolutePath = path.resolve(this.cwd, relPath)
44110
this.isEditing = true
45111

112+
// Update PostDiffviewBehaviorUtils context with current state
113+
this.postDiffBehaviorUtils.updateContext({
114+
relPath: relPath,
115+
editType: this.editType,
116+
documentWasOpen: this.documentWasOpen,
117+
preDiffActiveEditor: this.preDiffActiveEditor,
118+
})
119+
120+
// Track the URI of the actual file that will be part of the diff.
121+
// This ensures that if VS Code opens a tab for it during vscode.diff,
122+
// we can identify it as a "Roo-opened" tab for cleanup.
123+
const fileUriForDiff = vscode.Uri.file(absolutePath)
124+
this.rooOpenedTabs.add(fileUriForDiff.toString())
125+
46126
// If the file is already open, ensure it's not dirty before getting its
47127
// contents.
48128
if (fileExists) {
@@ -76,22 +156,18 @@ export class DiffViewProvider {
76156

77157
// If the file was already open, close it (must happen after showing the
78158
// diff view since if it's the only tab the column will close).
79-
this.documentWasOpen = false
80-
81-
// Close the tab if it's open (it's already saved above).
82-
const tabs = vscode.window.tabGroups.all
83-
.map((tg) => tg.tabs)
84-
.flat()
85-
.filter(
86-
(tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath),
87-
)
88-
89-
for (const tab of tabs) {
90-
if (!tab.isDirty) {
91-
await vscode.window.tabGroups.close(tab)
92-
}
93-
this.documentWasOpen = true
94-
}
159+
this.documentWasOpen =
160+
vscode.window.tabGroups.all
161+
.map((tg) => tg.tabs)
162+
.flat()
163+
.filter(
164+
(tab) =>
165+
tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath),
166+
).length > 0
167+
168+
this.postDiffBehaviorUtils.updateContext({
169+
documentWasOpen: this.documentWasOpen,
170+
})
95171

96172
this.activeDiffEditor = await this.openDiffEditor()
97173
this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor)
@@ -102,6 +178,17 @@ export class DiffViewProvider {
102178
this.streamedLines = []
103179
}
104180

181+
/**
182+
* Opens a file editor and tracks it as opened by Roo if not already open.
183+
*/
184+
private async showAndTrackEditor(uri: vscode.Uri, options: vscode.TextDocumentShowOptions = {}) {
185+
const editor = await this.showTextDocumentSafe({ uri, options })
186+
// Always track tabs opened by Roo, regardless of autoCloseTabs setting or if the document was already open.
187+
// The decision to close will be made in closeAllRooOpenedViews based on settings.
188+
this.rooOpenedTabs.add(uri.toString())
189+
return editor
190+
}
191+
105192
async update(accumulatedContent: string, isFinal: boolean) {
106193
if (!this.relPath || !this.activeLineController || !this.fadedOverlayController) {
107194
throw new Error("Required values not set")
@@ -197,8 +284,27 @@ export class DiffViewProvider {
197284
}
198285

199286
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })
200-
await this.closeAllDiffViews()
201287

288+
await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings())
289+
290+
// Implement post-diff focus behavior
291+
await this.postDiffBehaviorUtils.handlePostDiffFocus()
292+
293+
// If no auto-close settings are enabled and the document was not open before,
294+
// open the file after the diff is complete.
295+
296+
const settings = this._readDiffSettings() // Dynamically read settings
297+
298+
// If no auto-close settings are enabled and the document was not open before OR it's a new file,
299+
// open the file after the diff is complete.
300+
if (
301+
!settings.autoCloseRooTabs &&
302+
!settings.autoCloseAllRooTabs &&
303+
(this.editType === "create" || !this.documentWasOpen)
304+
) {
305+
const absolutePath = path.resolve(this.cwd, this.relPath!)
306+
await this.showAndTrackEditor(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })
307+
}
202308
// Getting diagnostics before and after the file edit is a better approach than
203309
// automatically tracking problems in real-time. This method ensures we only
204310
// report new problems that are a direct result of this specific edit.
@@ -344,7 +450,7 @@ export class DiffViewProvider {
344450
await updatedDocument.save()
345451
}
346452

347-
await this.closeAllDiffViews()
453+
await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings())
348454
await fs.unlink(absolutePath)
349455

350456
// Remove only the directories we created, in reverse order.
@@ -375,47 +481,19 @@ export class DiffViewProvider {
375481
})
376482
}
377483

378-
await this.closeAllDiffViews()
484+
await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings())
379485
}
380486

487+
// Implement post-diff focus behavior
488+
await this.postDiffBehaviorUtils.handlePostDiffFocus()
489+
381490
// Edit is done.
382491
await this.reset()
383492
}
384493

385-
private async closeAllDiffViews(): Promise<void> {
386-
const closeOps = vscode.window.tabGroups.all
387-
.flatMap((group) => group.tabs)
388-
.filter((tab) => {
389-
// Check for standard diff views with our URI scheme
390-
if (
391-
tab.input instanceof vscode.TabInputTextDiff &&
392-
tab.input.original.scheme === DIFF_VIEW_URI_SCHEME &&
393-
!tab.isDirty
394-
) {
395-
return true
396-
}
397-
398-
// Also check by tab label for our specific diff views
399-
// This catches cases where the diff view might be created differently
400-
// when files are pre-opened as text documents
401-
if (tab.label.includes(DIFF_VIEW_LABEL_CHANGES) && !tab.isDirty) {
402-
return true
403-
}
404-
405-
return false
406-
})
407-
.map((tab) =>
408-
vscode.window.tabGroups.close(tab).then(
409-
() => undefined,
410-
(err) => {
411-
console.error(`Failed to close diff tab ${tab.label}`, err)
412-
},
413-
),
414-
)
415-
416-
await Promise.all(closeOps)
417-
}
418-
494+
/**
495+
* Opens the diff editor
496+
*/
419497
private async openDiffEditor(): Promise<vscode.TextEditor> {
420498
if (!this.relPath) {
421499
throw new Error(
@@ -580,7 +658,13 @@ export class DiffViewProvider {
580658
}
581659

582660
async reset(): Promise<void> {
583-
await this.closeAllDiffViews()
661+
// Ensure any diff views opened by this provider are closed to release
662+
// memory.
663+
try {
664+
await this.postDiffBehaviorUtils.closeAllRooOpenedViews(this._readDiffSettings())
665+
} catch (error) {
666+
console.error("Error closing diff views", error)
667+
}
584668
this.editType = undefined
585669
this.isEditing = false
586670
this.originalContent = undefined
@@ -591,5 +675,7 @@ export class DiffViewProvider {
591675
this.activeLineController = undefined
592676
this.streamedLines = []
593677
this.preDiagnostics = []
678+
this.rooOpenedTabs.clear()
679+
594680
}
595681
}

0 commit comments

Comments
 (0)