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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export const globalSettingsSchema = z.object({
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
autoCloseRooTabs: z.boolean().optional(),
autoCloseAllRooTabs: z.boolean().optional(),
experiments: experimentsSchema.optional(),

codebaseIndexModels: codebaseIndexModelsSchema.optional(),
Expand Down
8 changes: 8 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,14 @@ export const webviewMessageHandler = async (
await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await provider.postStateToWebview()
break
case "autoCloseRooTabs":
await updateGlobalState("autoCloseRooTabs", message.bool ?? false)
await provider.postStateToWebview()
break
case "autoCloseAllRooTabs":
await updateGlobalState("autoCloseAllRooTabs", message.bool ?? false)
await provider.postStateToWebview()
break
case "hasOpenedModeSelector":
await updateGlobalState("hasOpenedModeSelector", message.bool ?? true)
await provider.postStateToWebview()
Expand Down
37 changes: 32 additions & 5 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Task } from "../../core/task/Task"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"

import { DecorationController } from "./DecorationController"
import { PostEditBehaviorUtils } from "./PostEditBehaviorUtils"

export const DIFF_VIEW_URI_SCHEME = "cline-diff"
export const DIFF_VIEW_LABEL_CHANGES = "Original ↔ Roo's Changes"
Expand All @@ -36,6 +37,7 @@ export class DiffViewProvider {
private activeLineController?: DecorationController
private streamedLines: string[] = []
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
private rooOpenedTabs: Set<string> = new Set()

constructor(private cwd: string) {}

Expand Down Expand Up @@ -95,6 +97,9 @@ export class DiffViewProvider {
this.documentWasOpen = true
}

// Track that we opened this file
this.rooOpenedTabs.add(absolutePath)

this.activeDiffEditor = await this.openDiffEditor()
this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor)
this.activeLineController = new DecorationController("activeLine", this.activeDiffEditor)
Expand Down Expand Up @@ -181,7 +186,12 @@ 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,
autoCloseRooTabs: boolean = false,
autoCloseAllRooTabs: boolean = false,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
Expand All @@ -201,6 +211,14 @@ export class DiffViewProvider {
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })
await this.closeAllDiffViews()

// Apply post-edit behavior (auto-close tabs if configured)
await PostEditBehaviorUtils.closeRooTabs(
autoCloseRooTabs,
autoCloseAllRooTabs,
this.rooOpenedTabs,
absolutePath,
)

// 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 All @@ -216,22 +234,22 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.

let newProblemsMessage = ""

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

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

const postDiagnostics = vscode.languages.getDiagnostics()

const newProblems = await diagnosticsToProblemsString(
Expand Down Expand Up @@ -610,5 +628,14 @@ export class DiffViewProvider {
this.activeLineController = undefined
this.streamedLines = []
this.preDiagnostics = []
this.rooOpenedTabs.clear()
}

/**
* Gets the set of file paths that were opened by Roo during the current session
* @returns Set of absolute file paths
*/
getRooOpenedTabs(): Set<string> {
return new Set(this.rooOpenedTabs)
}
}
135 changes: 135 additions & 0 deletions src/integrations/editor/PostEditBehaviorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as vscode from "vscode"
import { arePathsEqual } from "../../utils/path"
import { DIFF_VIEW_URI_SCHEME } from "./DiffViewProvider"

export class PostEditBehaviorUtils {
/**
* Closes Roo-related tabs based on the provided settings and tracked tabs
* @param autoCloseRooTabs - Close only tabs opened during the current task
* @param autoCloseAllRooTabs - Close all Roo tabs regardless of when they were opened
* @param rooOpenedTabs - Set of file paths that were opened by Roo during the current task
* @param editedFilePath - The path of the file that was just edited (to restore focus)
* @returns Promise<void>
*/
static async closeRooTabs(
autoCloseRooTabs: boolean,
autoCloseAllRooTabs: boolean,
rooOpenedTabs: Set<string>,
editedFilePath?: string,
): Promise<void> {
if (!autoCloseRooTabs && !autoCloseAllRooTabs) {
return
}

// Get all tabs across all tab groups
const allTabs = vscode.window.tabGroups.all.flatMap((group) => group.tabs)

// Filter tabs to close based on settings
const tabsToClose = allTabs.filter((tab) => {
// Check if it's a diff view tab
if (tab.input instanceof vscode.TabInputTextDiff && tab.input.original.scheme === DIFF_VIEW_URI_SCHEME) {
return true
}

// Check if it's a regular text tab
if (tab.input instanceof vscode.TabInputText) {
const tabPath = tab.input.uri.fsPath

// Don't close the file that was just edited
if (editedFilePath && arePathsEqual(tabPath, editedFilePath)) {
return false
}

if (autoCloseAllRooTabs) {
// Close all Roo tabs - for now, we consider all tabs that were tracked
// In a more sophisticated implementation, we might check for Roo-specific markers
return rooOpenedTabs.has(tabPath)
} else if (autoCloseRooTabs) {
// Close only tabs opened during the current task
return rooOpenedTabs.has(tabPath)
}
}

return false
})

// Close the tabs
const closePromises = tabsToClose.map((tab) => {
if (!tab.isDirty) {
return vscode.window.tabGroups.close(tab).then(
() => undefined,
(err: any) => {
console.error(`Failed to close tab ${tab.label}:`, err)
},
)
}
return Promise.resolve()
})

await Promise.all(closePromises)

// Restore focus to the edited file if provided
if (editedFilePath) {
try {
await vscode.window.showTextDocument(vscode.Uri.file(editedFilePath), {
preview: false,
preserveFocus: false,
})
} catch (err) {
console.error(`Failed to restore focus to ${editedFilePath}:`, err)
}
}
}

/**
* Determines which tabs should be closed based on the filter criteria
* @param tabs - Array of tabs to filter
* @param filter - Filter criteria (all Roo tabs or only current task tabs)
* @param rooOpenedTabs - Set of file paths opened by Roo
* @returns Array of tabs that match the filter criteria
*/
static filterTabsToClose(
tabs: readonly vscode.Tab[],
filter: "all" | "current",
rooOpenedTabs: Set<string>,
): vscode.Tab[] {
return tabs.filter((tab) => {
// Always close diff view tabs
if (tab.input instanceof vscode.TabInputTextDiff && tab.input.original.scheme === DIFF_VIEW_URI_SCHEME) {
return true
}

// For text tabs, apply the filter
if (tab.input instanceof vscode.TabInputText) {
const tabPath = tab.input.uri.fsPath

if (filter === "all") {
// In a real implementation, we might have additional checks
// to identify Roo-specific tabs beyond just the tracked set
return true
} else if (filter === "current") {
return rooOpenedTabs.has(tabPath)
}
}

return false
})
}

/**
* Checks if a tab is a Roo-related tab
* @param tab - The tab to check
* @returns true if the tab is Roo-related
*/
static isRooTab(tab: vscode.Tab): boolean {
// Check if it's a diff view tab
if (tab.input instanceof vscode.TabInputTextDiff && tab.input.original.scheme === DIFF_VIEW_URI_SCHEME) {
return true
}

// Additional checks could be added here to identify other Roo-specific tabs
// For example, checking for specific URI schemes, file patterns, etc.

return false
}
}
Loading
Loading