diff --git a/packages/types/src/__tests__/index.test.ts b/packages/types/src/__tests__/index.test.ts index 15441d48fd..a8e2e25b69 100644 --- a/packages/types/src/__tests__/index.test.ts +++ b/packages/types/src/__tests__/index.test.ts @@ -22,4 +22,8 @@ describe("GLOBAL_STATE_KEYS", () => { it("should not contain OpenAI Compatible API key (secret)", () => { expect(GLOBAL_STATE_KEYS).not.toContain("codebaseIndexOpenAiCompatibleApiKey") }) + + it("should contain openTabsAtEndOfList setting", () => { + expect(GLOBAL_STATE_KEYS).toContain("openTabsAtEndOfList") + }) }) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a30550dce1..9c346e97a1 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -108,6 +108,7 @@ export const globalSettingsSchema = z.object({ rateLimitSeconds: z.number().optional(), diffEnabled: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), + openTabsAtEndOfList: z.boolean().optional(), experiments: experimentsSchema.optional(), codebaseIndexModels: codebaseIndexModelsSchema.optional(), @@ -251,6 +252,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { diffEnabled: true, fuzzyMatchThreshold: 1, + openTabsAtEndOfList: false, enableCheckpoints: false, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 53b8ef5b87..eb4cd341a6 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -260,7 +260,21 @@ export class Task extends EventEmitter { this.consecutiveMistakeLimit = consecutiveMistakeLimit ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT this.providerRef = new WeakRef(provider) this.globalStoragePath = provider.context.globalStorageUri.fsPath - this.diffViewProvider = new DiffViewProvider(this.cwd) + + // Get openTabsAtEndOfList setting asynchronously + provider + .getState() + .then((state) => { + const openTabsAtEndOfList = state?.openTabsAtEndOfList ?? false + this.diffViewProvider = new DiffViewProvider(this.cwd, openTabsAtEndOfList) + }) + .catch((error) => { + console.error("Failed to get openTabsAtEndOfList setting:", error) + this.diffViewProvider = new DiffViewProvider(this.cwd, false) + }) + + // Initialize with default value for immediate use + this.diffViewProvider = new DiffViewProvider(this.cwd, false) this.enableCheckpoints = enableCheckpoints this.rootTask = rootTask diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 9aa5a8d7a8..6b5de1436a 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -116,6 +116,7 @@ vi.mock("vscode", () => { stat: vi.fn().mockResolvedValue({ type: 1 }), // FileType.File = 1 }, onDidSaveTextDocument: vi.fn(() => mockDisposable), + onDidChangeWorkspaceFolders: vi.fn(() => mockDisposable), getConfiguration: vi.fn(() => ({ get: (key: string, defaultValue: any) => defaultValue })), }, env: { @@ -127,6 +128,25 @@ vi.mock("vscode", () => { from: vi.fn(), }, TabInputText: vi.fn(), + Uri: { + file: vi.fn((path: string) => ({ + fsPath: path, + scheme: "file", + authority: "", + path, + query: "", + fragment: "", + })), + parse: vi.fn((str: string) => ({ + fsPath: str, + scheme: "file", + authority: "", + path: str, + query: "", + fragment: "", + })), + }, + RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })), } }) @@ -1313,7 +1333,7 @@ describe("Cline", () => { context: { globalStorageUri: { fsPath: "/test/storage" }, }, - getState: vi.fn(), + getState: vi.fn().mockResolvedValue({}), } }) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6231f08167..60a8329d9d 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1440,6 +1440,7 @@ export class ClineProvider alwaysAllowFollowupQuestions, followupAutoApproveTimeoutMs, diagnosticsEnabled, + openTabsAtEndOfList, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1561,6 +1562,7 @@ export class ClineProvider alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false, followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000, diagnosticsEnabled: diagnosticsEnabled ?? true, + openTabsAtEndOfList: openTabsAtEndOfList ?? false, } } @@ -1726,6 +1728,7 @@ export class ClineProvider codebaseIndexSearchMinScore: stateValues.codebaseIndexConfig?.codebaseIndexSearchMinScore, }, profileThresholds: stateValues.profileThresholds ?? {}, + openTabsAtEndOfList: stateValues.openTabsAtEndOfList ?? false, } } diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index f4133029c9..a5efec428e 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -36,8 +36,14 @@ export class DiffViewProvider { private activeLineController?: DecorationController private streamedLines: string[] = [] private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [] + private openTabsAtEndOfList: boolean - constructor(private cwd: string) {} + constructor( + private cwd: string, + openTabsAtEndOfList: boolean = false, + ) { + this.openTabsAtEndOfList = openTabsAtEndOfList + } async open(relPath: string): Promise { this.relPath = relPath @@ -181,7 +187,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 @@ -198,7 +207,11 @@ export class DiffViewProvider { await updatedDocument.save() } - await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true }) + await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { + preview: false, + preserveFocus: true, + viewColumn: this.openTabsAtEndOfList ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active, + }) await this.closeAllDiffViews() // Getting diagnostics before and after the file edit is a better approach than @@ -216,22 +229,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( @@ -391,6 +404,7 @@ export class DiffViewProvider { await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true, + viewColumn: this.openTabsAtEndOfList ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active, }) } @@ -523,7 +537,11 @@ 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: this.openTabsAtEndOfList ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active, + preserveFocus: true, + }) .then(() => { // Execute the diff command after ensuring the file is open as text return vscode.commands.executeCommand( @@ -533,7 +551,10 @@ export class DiffViewProvider { }), uri, `${fileName}: ${fileExists ? `${DIFF_VIEW_LABEL_CHANGES}` : "New File"} (Editable)`, - { preserveFocus: true }, + { + preserveFocus: true, + viewColumn: this.openTabsAtEndOfList ? vscode.ViewColumn.Beside : undefined, + }, ) }) .then(