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
4 changes: 4 additions & 0 deletions packages/types/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
})
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,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(),
Expand Down Expand Up @@ -251,6 +252,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {

diffEnabled: true,
fuzzyMatchThreshold: 1,
openTabsAtEndOfList: false,

enableCheckpoints: false,

Expand Down
16 changes: 15 additions & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,21 @@ 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)

// 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
Expand Down
22 changes: 21 additions & 1 deletion src/core/task/__tests__/Task.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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 })),
}
})

Expand Down Expand Up @@ -1313,7 +1333,7 @@ describe("Cline", () => {
context: {
globalStorageUri: { fsPath: "/test/storage" },
},
getState: vi.fn(),
getState: vi.fn().mockResolvedValue({}),
}
})

Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,7 @@ export class ClineProvider
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
diagnosticsEnabled,
openTabsAtEndOfList,
} = await this.getState()

const telemetryKey = process.env.POSTHOG_API_KEY
Expand Down Expand Up @@ -1561,6 +1562,7 @@ export class ClineProvider
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
diagnosticsEnabled: diagnosticsEnabled ?? true,
openTabsAtEndOfList: openTabsAtEndOfList ?? false,
}
}

Expand Down Expand Up @@ -1726,6 +1728,7 @@ export class ClineProvider
codebaseIndexSearchMinScore: stateValues.codebaseIndexConfig?.codebaseIndexSearchMinScore,
},
profileThresholds: stateValues.profileThresholds ?? {},
openTabsAtEndOfList: stateValues.openTabsAtEndOfList ?? false,
}
}

Expand Down
39 changes: 30 additions & 9 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
this.relPath = relPath
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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,
})
}

Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down