Skip to content

Commit 9dd88bb

Browse files
committed
feat: Add openTabsInCorrectGroup setting for better tab management
- Added openTabsInCorrectGroup setting to GlobalState type definition - Created UI toggle in AutoApproveSettings component within File Editing Options section - Added translation strings for the new UI elements - Implemented message handler in webviewMessageHandler.ts - Modified DiffViewProvider to accept provider reference and implement tab placement logic - Updated Task.ts to pass provider to DiffViewProvider - Added setting to ExtensionState and WebviewMessage types - Fixed TypeScript errors by adding openTabsInCorrectGroup to ClineProvider getState() This feature allows Roo to open diff editors and related tabs in the most logical editor group when working with multiple VS Code editor groups, improving navigation and organization for users with complex editor layouts. Fixes #6005
1 parent 9fce90b commit 9dd88bb

File tree

11 files changed

+120
-11
lines changed

11 files changed

+120
-11
lines changed

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const globalSettingsSchema = z.object({
107107

108108
rateLimitSeconds: z.number().optional(),
109109
diffEnabled: z.boolean().optional(),
110+
openTabsInCorrectGroup: z.boolean().optional(),
110111
fuzzyMatchThreshold: z.number().optional(),
111112
experiments: experimentsSchema.optional(),
112113

src/core/task/Task.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export class Task extends EventEmitter<ClineEvents> {
260260
this.consecutiveMistakeLimit = consecutiveMistakeLimit ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
261261
this.providerRef = new WeakRef(provider)
262262
this.globalStoragePath = provider.context.globalStorageUri.fsPath
263-
this.diffViewProvider = new DiffViewProvider(this.cwd)
263+
this.diffViewProvider = new DiffViewProvider(this.cwd, provider)
264264
this.enableCheckpoints = enableCheckpoints
265265

266266
this.rootTask = rootTask

src/core/webview/ClineProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,7 @@ export class ClineProvider
17261726
codebaseIndexSearchMinScore: stateValues.codebaseIndexConfig?.codebaseIndexSearchMinScore,
17271727
},
17281728
profileThresholds: stateValues.profileThresholds ?? {},
1729+
openTabsInCorrectGroup: stateValues.openTabsInCorrectGroup ?? false,
17291730
}
17301731
}
17311732

src/core/webview/webviewMessageHandler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,11 @@ export const webviewMessageHandler = async (
932932
await updateGlobalState("diffEnabled", diffEnabled)
933933
await provider.postStateToWebview()
934934
break
935+
case "openTabsInCorrectGroup":
936+
const openTabsInCorrectGroup = message.bool ?? false
937+
await updateGlobalState("openTabsInCorrectGroup", openTabsInCorrectGroup)
938+
await provider.postStateToWebview()
939+
break
935940
case "enableCheckpoints":
936941
const enableCheckpoints = message.bool ?? true
937942
await updateGlobalState("enableCheckpoints", enableCheckpoints)

src/integrations/editor/DiffViewProvider.ts

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
1313
import { ClineSayTool } from "../../shared/ExtensionMessage"
1414
import { Task } from "../../core/task/Task"
1515
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
16+
import { ClineProvider } from "../../core/webview/ClineProvider"
1617

1718
import { DecorationController } from "./DecorationController"
1819

@@ -36,8 +37,16 @@ export class DiffViewProvider {
3637
private activeLineController?: DecorationController
3738
private streamedLines: string[] = []
3839
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
39-
40-
constructor(private cwd: string) {}
40+
private providerRef?: WeakRef<ClineProvider>
41+
42+
constructor(
43+
private cwd: string,
44+
provider?: ClineProvider,
45+
) {
46+
if (provider) {
47+
this.providerRef = new WeakRef(provider)
48+
}
49+
}
4150

4251
async open(relPath: string): Promise<void> {
4352
this.relPath = relPath
@@ -181,7 +190,10 @@ export class DiffViewProvider {
181190
}
182191
}
183192

184-
async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
193+
async saveChanges(
194+
diagnosticsEnabled: boolean = true,
195+
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
196+
): Promise<{
185197
newProblemsMessage: string | undefined
186198
userEdits: string | undefined
187199
finalContent: string | undefined
@@ -216,22 +228,22 @@ export class DiffViewProvider {
216228
// and can address them accordingly. If problems don't change immediately after
217229
// applying a fix, won't be notified, which is generally fine since the
218230
// initial fix is usually correct and it may just take time for linters to catch up.
219-
231+
220232
let newProblemsMessage = ""
221-
233+
222234
if (diagnosticsEnabled) {
223235
// Add configurable delay to allow linters time to process and clean up issues
224236
// like unused imports (especially important for Go and other languages)
225237
// Ensure delay is non-negative
226238
const safeDelayMs = Math.max(0, writeDelayMs)
227-
239+
228240
try {
229241
await delay(safeDelayMs)
230242
} catch (error) {
231243
// Log error but continue - delay failure shouldn't break the save operation
232244
console.warn(`Failed to apply write delay: ${error}`)
233245
}
234-
246+
235247
const postDiagnostics = vscode.languages.getDiagnostics()
236248

237249
const newProblems = await diagnosticsToProblemsString(
@@ -461,6 +473,53 @@ export class DiffViewProvider {
461473
return editor
462474
}
463475

476+
// Determine the view column based on the openTabsInCorrectGroup setting
477+
let targetViewColumn = vscode.ViewColumn.Active
478+
479+
// Check if we should open in the same group as the original file
480+
const provider = this.providerRef?.deref()
481+
if (provider) {
482+
const state = await provider.getState()
483+
const openTabsInCorrectGroup = state?.openTabsInCorrectGroup ?? false
484+
485+
if (openTabsInCorrectGroup) {
486+
// Find which tab group contains the original file
487+
const originalFileTab = vscode.window.tabGroups.all
488+
.flatMap((group) => group.tabs.map((tab) => ({ tab, group })))
489+
.find(
490+
({ tab }) =>
491+
tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath),
492+
)
493+
494+
if (originalFileTab) {
495+
// Use the view column of the group containing the original file
496+
targetViewColumn = originalFileTab.group.viewColumn
497+
} else {
498+
// If the original file isn't open, try to find the most logical group
499+
// This could be the group with the most related files (same directory)
500+
const fileDir = path.dirname(uri.fsPath)
501+
const groupsWithRelatedFiles = vscode.window.tabGroups.all.map((group) => {
502+
const relatedFilesCount = group.tabs.filter((tab) => {
503+
if (tab.input instanceof vscode.TabInputText) {
504+
const tabDir = path.dirname(tab.input.uri.fsPath)
505+
return tabDir === fileDir
506+
}
507+
return false
508+
}).length
509+
return { group, relatedFilesCount }
510+
})
511+
512+
// Sort by most related files
513+
groupsWithRelatedFiles.sort((a, b) => b.relatedFilesCount - a.relatedFilesCount)
514+
515+
// Use the group with the most related files, or fall back to active
516+
if (groupsWithRelatedFiles.length > 0 && groupsWithRelatedFiles[0].relatedFilesCount > 0) {
517+
targetViewColumn = groupsWithRelatedFiles[0].group.viewColumn
518+
}
519+
}
520+
}
521+
}
522+
464523
// Open new diff editor.
465524
return new Promise<vscode.TextEditor>((resolve, reject) => {
466525
const fileName = path.basename(uri.fsPath)
@@ -523,7 +582,7 @@ export class DiffViewProvider {
523582
// Pre-open the file as a text document to ensure it doesn't open in preview mode
524583
// This fixes issues with files that have custom editor associations (like markdown preview)
525584
vscode.window
526-
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
585+
.showTextDocument(uri, { preview: false, viewColumn: targetViewColumn, preserveFocus: true })
527586
.then(() => {
528587
// Execute the diff command after ensuring the file is open as text
529588
return vscode.commands.executeCommand(
@@ -533,7 +592,7 @@ export class DiffViewProvider {
533592
}),
534593
uri,
535594
`${fileName}: ${fileExists ? `${DIFF_VIEW_LABEL_CHANGES}` : "New File"} (Editable)`,
536-
{ preserveFocus: true },
595+
{ preserveFocus: true, viewColumn: targetViewColumn },
537596
)
538597
})
539598
.then(

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export type ExtensionState = Pick<
217217
| "terminalCompressProgressBar"
218218
| "diagnosticsEnabled"
219219
| "diffEnabled"
220+
| "openTabsInCorrectGroup"
220221
| "fuzzyMatchThreshold"
221222
// | "experiments" // Optional in GlobalSettings, required here.
222223
| "language"

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface WebviewMessage {
9393
| "ttsSpeed"
9494
| "soundVolume"
9595
| "diffEnabled"
96+
| "openTabsInCorrectGroup"
9697
| "enableCheckpoints"
9798
| "browserViewportSize"
9899
| "screenshotQuality"

webview-ui/src/components/settings/AutoApproveSettings.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
3333
followupAutoApproveTimeoutMs?: number
3434
allowedCommands?: string[]
3535
deniedCommands?: string[]
36+
openTabsInCorrectGroup?: boolean
3637
setCachedStateField: SetCachedStateField<
3738
| "alwaysAllowReadOnly"
3839
| "alwaysAllowReadOnlyOutsideWorkspace"
@@ -52,6 +53,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
5253
| "allowedCommands"
5354
| "deniedCommands"
5455
| "alwaysAllowUpdateTodoList"
56+
| "openTabsInCorrectGroup"
5557
>
5658
}
5759

@@ -74,6 +76,7 @@ export const AutoApproveSettings = ({
7476
alwaysAllowUpdateTodoList,
7577
allowedCommands,
7678
deniedCommands,
79+
openTabsInCorrectGroup,
7780
setCachedStateField,
7881
...props
7982
}: AutoApproveSettingsProps) => {
@@ -393,6 +396,27 @@ export const AutoApproveSettings = ({
393396
</div>
394397
</div>
395398
)}
399+
400+
{/* FILE EDITING OPTIONS */}
401+
<div className="mt-6">
402+
<div className="flex items-center gap-4 font-bold mb-3">
403+
<span className="codicon codicon-file-code" />
404+
<div>{t("settings:autoApprove.fileEditing.label")}</div>
405+
</div>
406+
<div className="pl-3 border-l-2 border-vscode-button-background">
407+
<VSCodeCheckbox
408+
checked={openTabsInCorrectGroup}
409+
onChange={(e: any) => setCachedStateField("openTabsInCorrectGroup", e.target.checked)}
410+
data-testid="open-tabs-in-correct-group-checkbox">
411+
<span className="font-medium">
412+
{t("settings:autoApprove.fileEditing.openTabsInCorrectGroup.label")}
413+
</span>
414+
</VSCodeCheckbox>
415+
<div className="text-vscode-descriptionForeground text-sm mt-1">
416+
{t("settings:autoApprove.fileEditing.openTabsInCorrectGroup.description")}
417+
</div>
418+
</div>
419+
</div>
396420
</Section>
397421
</div>
398422
)

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
177177
alwaysAllowFollowupQuestions,
178178
alwaysAllowUpdateTodoList,
179179
followupAutoApproveTimeoutMs,
180+
openTabsInCorrectGroup,
180181
} = cachedState
181182

182183
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@@ -300,6 +301,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
300301
vscode.postMessage({ type: "remoteBrowserEnabled", bool: remoteBrowserEnabled })
301302
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
302303
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
304+
vscode.postMessage({ type: "openTabsInCorrectGroup", bool: openTabsInCorrectGroup })
303305
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
304306
vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 })
305307
vscode.postMessage({ type: "terminalOutputCharacterLimit", value: terminalOutputCharacterLimit ?? 50000 })
@@ -619,6 +621,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
619621
followupAutoApproveTimeoutMs={followupAutoApproveTimeoutMs}
620622
allowedCommands={allowedCommands}
621623
deniedCommands={deniedCommands}
624+
openTabsInCorrectGroup={openTabsInCorrectGroup}
622625
setCachedStateField={setCachedStateField}
623626
/>
624627
)}

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ export interface ExtensionStateContextType extends ExtensionState {
134134
routerModels?: RouterModels
135135
alwaysAllowUpdateTodoList?: boolean
136136
setAlwaysAllowUpdateTodoList: (value: boolean) => void
137+
openTabsInCorrectGroup?: boolean
138+
setOpenTabsInCorrectGroup: (value: boolean) => void
137139
}
138140

139141
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@@ -229,6 +231,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
229231
},
230232
codebaseIndexModels: { ollama: {}, openai: {} },
231233
alwaysAllowUpdateTodoList: true,
234+
openTabsInCorrectGroup: false,
232235
})
233236

234237
const [didHydrateState, setDidHydrateState] = useState(false)
@@ -474,6 +477,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
474477
setAlwaysAllowUpdateTodoList: (value) => {
475478
setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value }))
476479
},
480+
openTabsInCorrectGroup: state.openTabsInCorrectGroup,
481+
setOpenTabsInCorrectGroup: (value) => {
482+
setState((prevState) => ({ ...prevState, openTabsInCorrectGroup: value }))
483+
},
477484
}
478485

479486
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>

0 commit comments

Comments
 (0)