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
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const globalSettingsSchema = z.object({

rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
openTabsInCorrectGroup: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
experiments: experimentsSchema.optional(),

Expand Down
2 changes: 1 addition & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class Task extends EventEmitter<ClineEvents> {
this.consecutiveMistakeLimit = consecutiveMistakeLimit ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
this.providerRef = new WeakRef(provider)
this.globalStoragePath = provider.context.globalStorageUri.fsPath
this.diffViewProvider = new DiffViewProvider(this.cwd)
this.diffViewProvider = new DiffViewProvider(this.cwd, provider)
this.enableCheckpoints = enableCheckpoints

this.rootTask = rootTask
Expand Down
1 change: 1 addition & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,7 @@ export class ClineProvider
codebaseIndexSearchMinScore: stateValues.codebaseIndexConfig?.codebaseIndexSearchMinScore,
},
profileThresholds: stateValues.profileThresholds ?? {},
openTabsInCorrectGroup: stateValues.openTabsInCorrectGroup ?? false,
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,11 @@ export const webviewMessageHandler = async (
await updateGlobalState("diffEnabled", diffEnabled)
await provider.postStateToWebview()
break
case "openTabsInCorrectGroup":
const openTabsInCorrectGroup = message.bool ?? false
await updateGlobalState("openTabsInCorrectGroup", openTabsInCorrectGroup)
await provider.postStateToWebview()
break
case "enableCheckpoints":
const enableCheckpoints = message.bool ?? true
await updateGlobalState("enableCheckpoints", enableCheckpoints)
Expand Down
77 changes: 68 additions & 9 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { Task } from "../../core/task/Task"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { ClineProvider } from "../../core/webview/ClineProvider"

import { DecorationController } from "./DecorationController"

Expand All @@ -36,8 +37,16 @@ export class DiffViewProvider {
private activeLineController?: DecorationController
private streamedLines: string[] = []
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []

constructor(private cwd: string) {}
private providerRef?: WeakRef<ClineProvider>

constructor(
private cwd: string,
provider?: ClineProvider,
) {
if (provider) {
this.providerRef = new WeakRef(provider)
}
}

async open(relPath: string): Promise<void> {
this.relPath = relPath
Expand Down Expand Up @@ -181,7 +190,10 @@ 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,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
Expand Down Expand Up @@ -216,22 +228,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 @@ -461,6 +473,53 @@ export class DiffViewProvider {
return editor
}

// Determine the view column based on the openTabsInCorrectGroup setting
let targetViewColumn = vscode.ViewColumn.Active

// Check if we should open in the same group as the original file
const provider = this.providerRef?.deref()
if (provider) {
const state = await provider.getState()
const openTabsInCorrectGroup = state?.openTabsInCorrectGroup ?? false

if (openTabsInCorrectGroup) {
// Find which tab group contains the original file
const originalFileTab = vscode.window.tabGroups.all
.flatMap((group) => group.tabs.map((tab) => ({ tab, group })))
.find(
({ tab }) =>
tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath),
)

if (originalFileTab) {
// Use the view column of the group containing the original file
targetViewColumn = originalFileTab.group.viewColumn
} else {
// If the original file isn't open, try to find the most logical group
// This could be the group with the most related files (same directory)
const fileDir = path.dirname(uri.fsPath)
const groupsWithRelatedFiles = vscode.window.tabGroups.all.map((group) => {
const relatedFilesCount = group.tabs.filter((tab) => {
if (tab.input instanceof vscode.TabInputText) {
const tabDir = path.dirname(tab.input.uri.fsPath)
return tabDir === fileDir
}
return false
}).length
return { group, relatedFilesCount }
})

// Sort by most related files
groupsWithRelatedFiles.sort((a, b) => b.relatedFilesCount - a.relatedFilesCount)

// Use the group with the most related files, or fall back to active
if (groupsWithRelatedFiles.length > 0 && groupsWithRelatedFiles[0].relatedFilesCount > 0) {
targetViewColumn = groupsWithRelatedFiles[0].group.viewColumn
}
}
}
}

// Open new diff editor.
return new Promise<vscode.TextEditor>((resolve, reject) => {
const fileName = path.basename(uri.fsPath)
Expand Down Expand Up @@ -523,7 +582,7 @@ export class DiffViewProvider {
// Pre-open the file as a text document to ensure it doesn't open in preview mode
// This fixes issues with files that have custom editor associations (like markdown preview)
vscode.window
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
.showTextDocument(uri, { preview: false, viewColumn: targetViewColumn, preserveFocus: true })
.then(() => {
// Execute the diff command after ensuring the file is open as text
return vscode.commands.executeCommand(
Expand All @@ -533,7 +592,7 @@ export class DiffViewProvider {
}),
uri,
`${fileName}: ${fileExists ? `${DIFF_VIEW_LABEL_CHANGES}` : "New File"} (Editable)`,
{ preserveFocus: true },
{ preserveFocus: true, viewColumn: targetViewColumn },
)
})
.then(
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export type ExtensionState = Pick<
| "terminalCompressProgressBar"
| "diagnosticsEnabled"
| "diffEnabled"
| "openTabsInCorrectGroup"
| "fuzzyMatchThreshold"
// | "experiments" // Optional in GlobalSettings, required here.
| "language"
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface WebviewMessage {
| "ttsSpeed"
| "soundVolume"
| "diffEnabled"
| "openTabsInCorrectGroup"
| "enableCheckpoints"
| "browserViewportSize"
| "screenshotQuality"
Expand Down
24 changes: 24 additions & 0 deletions webview-ui/src/components/settings/AutoApproveSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
followupAutoApproveTimeoutMs?: number
allowedCommands?: string[]
deniedCommands?: string[]
openTabsInCorrectGroup?: boolean
setCachedStateField: SetCachedStateField<
| "alwaysAllowReadOnly"
| "alwaysAllowReadOnlyOutsideWorkspace"
Expand All @@ -52,6 +53,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
| "allowedCommands"
| "deniedCommands"
| "alwaysAllowUpdateTodoList"
| "openTabsInCorrectGroup"
>
}

Expand All @@ -74,6 +76,7 @@ export const AutoApproveSettings = ({
alwaysAllowUpdateTodoList,
allowedCommands,
deniedCommands,
openTabsInCorrectGroup,
setCachedStateField,
...props
}: AutoApproveSettingsProps) => {
Expand Down Expand Up @@ -393,6 +396,27 @@ export const AutoApproveSettings = ({
</div>
</div>
)}

{/* FILE EDITING OPTIONS */}
<div className="mt-6">
<div className="flex items-center gap-4 font-bold mb-3">
<span className="codicon codicon-file-code" />
<div>{t("settings:autoApprove.fileEditing.label")}</div>
</div>
<div className="pl-3 border-l-2 border-vscode-button-background">
<VSCodeCheckbox
checked={openTabsInCorrectGroup}
onChange={(e: any) => setCachedStateField("openTabsInCorrectGroup", e.target.checked)}
data-testid="open-tabs-in-correct-group-checkbox">
<span className="font-medium">
{t("settings:autoApprove.fileEditing.openTabsInCorrectGroup.label")}
</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:autoApprove.fileEditing.openTabsInCorrectGroup.description")}
</div>
</div>
</div>
</Section>
</div>
)
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
followupAutoApproveTimeoutMs,
openTabsInCorrectGroup,
} = cachedState

const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
Expand Down Expand Up @@ -300,6 +301,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "remoteBrowserEnabled", bool: remoteBrowserEnabled })
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
vscode.postMessage({ type: "openTabsInCorrectGroup", bool: openTabsInCorrectGroup })
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 })
vscode.postMessage({ type: "terminalOutputCharacterLimit", value: terminalOutputCharacterLimit ?? 50000 })
Expand Down Expand Up @@ -619,6 +621,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
followupAutoApproveTimeoutMs={followupAutoApproveTimeoutMs}
allowedCommands={allowedCommands}
deniedCommands={deniedCommands}
openTabsInCorrectGroup={openTabsInCorrectGroup}
setCachedStateField={setCachedStateField}
/>
)}
Expand Down
7 changes: 7 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ export interface ExtensionStateContextType extends ExtensionState {
routerModels?: RouterModels
alwaysAllowUpdateTodoList?: boolean
setAlwaysAllowUpdateTodoList: (value: boolean) => void
openTabsInCorrectGroup?: boolean
setOpenTabsInCorrectGroup: (value: boolean) => void
}

export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
Expand Down Expand Up @@ -229,6 +231,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
},
codebaseIndexModels: { ollama: {}, openai: {} },
alwaysAllowUpdateTodoList: true,
openTabsInCorrectGroup: false,
})

const [didHydrateState, setDidHydrateState] = useState(false)
Expand Down Expand Up @@ -474,6 +477,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setAlwaysAllowUpdateTodoList: (value) => {
setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value }))
},
openTabsInCorrectGroup: state.openTabsInCorrectGroup,
setOpenTabsInCorrectGroup: (value) => {
setState((prevState) => ({ ...prevState, openTabsInCorrectGroup: value }))
},
}

return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
Expand Down
9 changes: 8 additions & 1 deletion webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,14 @@
},
"toggleAriaLabel": "Toggle auto-approval",
"disabledAriaLabel": "Auto-approval disabled - select options first",
"selectOptionsFirst": "Select at least one option below to enable auto-approval"
"selectOptionsFirst": "Select at least one option below to enable auto-approval",
"fileEditing": {
"label": "File Editing Options",
"openTabsInCorrectGroup": {
"label": "Open tabs in correct group",
"description": "When enabled, Roo will open diff tabs beside the original file in the same editor group. When disabled, tabs open in the currently active group."
}
}
},
"providers": {
"providerDocumentation": "{{provider}} documentation",
Expand Down
Loading