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
52 changes: 52 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,58 @@ export class ClineProvider
return this.mcpHub
}

/**
* Handle workspace folder change by re-initializing the CodeIndexManager
* This ensures the code index corresponds to the currently active workspace folder
*/
public async handleWorkspaceFolderChange() {
if (!this.codeIndexManager) {
return
}

try {
// Dispose of the old code index status subscription
if (this.codeIndexStatusSubscription) {
this.codeIndexStatusSubscription.dispose()
this.codeIndexStatusSubscription = undefined
}

// Get a new instance of CodeIndexManager for the current workspace
const newManager = CodeIndexManager.getInstance(this.context)
if (!newManager) {
this.log("No workspace folder available for code index")
return
}

// Re-initialize the manager
await newManager.initialize(this.contextProxy)

// Re-subscribe to status updates
this.codeIndexStatusSubscription = newManager.onProgressUpdate((update: IndexProgressUpdate) => {
this.postMessageToWebview({
type: "indexingStatusUpdate",
values: update,
})
})
if (this.webviewDisposables && this.codeIndexStatusSubscription) {
this.webviewDisposables.push(this.codeIndexStatusSubscription)
}

// Update the reference
;(this as any).codeIndexManager = newManager

// Send the current status to the webview
this.postMessageToWebview({
type: "indexingStatusUpdate",
values: newManager.getCurrentStatus(),
})

this.log(`Code index re-initialized for workspace: ${getWorkspacePath()}`)
} catch (error) {
this.log(`Failed to re-initialize code index: ${error}`)
}
}

/**
* Check if the current state is compliant with MDM policy
* @returns true if compliant, false if blocked
Expand Down
15 changes: 10 additions & 5 deletions src/integrations/workspace/WorkspaceTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,16 @@ class WorkspaceTracker {
}
this.resetTimer = setTimeout(async () => {
if (this.prevWorkSpacePath !== this.cwd) {
await this.providerRef.deref()?.postMessageToWebview({
type: "workspaceUpdated",
filePaths: [],
openedTabs: this.getOpenedTabsInfo(),
})
const provider = this.providerRef.deref()
if (provider) {
await provider.postMessageToWebview({
type: "workspaceUpdated",
filePaths: [],
openedTabs: this.getOpenedTabsInfo(),
})
// Trigger code index re-initialization for the new workspace
await provider.handleWorkspaceFolderChange()
}
this.filePaths.clear()
this.prevWorkSpacePath = this.cwd
this.initializeFilePaths()
Expand Down
18 changes: 14 additions & 4 deletions src/integrations/workspace/__tests__/WorkspaceTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ describe("WorkspaceTracker", () => {
// Create provider mock
mockProvider = {
postMessageToWebview: vitest.fn().mockResolvedValue(undefined),
} as unknown as ClineProvider & { postMessageToWebview: Mock }
handleWorkspaceFolderChange: vitest.fn().mockResolvedValue(undefined),
} as unknown as ClineProvider & { postMessageToWebview: Mock; handleWorkspaceFolderChange: Mock }

// Create tracker instance
workspaceTracker = new WorkspaceTracker(mockProvider)
Expand Down Expand Up @@ -225,23 +226,32 @@ describe("WorkspaceTracker", () => {
// Simulate tab change event
await registeredTabChangeCallback!()

// Run the debounce timer for workspaceDidReset
// Run the debounce timer for workspaceDidReset (300ms)
vitest.advanceTimersByTime(300)

// Wait for the async operations in the timeout callback to complete
await Promise.resolve() // For the setTimeout callback
await Promise.resolve() // For the async operations inside
await Promise.resolve() // For handleWorkspaceFolderChange

// Should clear file paths and reset workspace
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "workspaceUpdated",
filePaths: [],
openedTabs: [],
})

// Should have called handleWorkspaceFolderChange
expect(mockProvider.handleWorkspaceFolderChange).toHaveBeenCalled()

// Run all remaining timers to complete initialization
await Promise.resolve() // Wait for initializeFilePaths to complete
vitest.runAllTimers()

// Wait for initializeFilePaths to complete
await Promise.resolve()

// Should initialize file paths for new workspace
expect(listFiles).toHaveBeenCalledWith("/test/new-workspace", true, 1000)
vitest.runAllTimers()
})

it("should not update file paths if workspace changes during initialization", async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/services/code-index/__tests__/manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ vi.mock("vscode", () => ({
},
],
},
window: {
activeTextEditor: undefined,
},
}))

// Mock only the essential dependencies
Expand Down
13 changes: 5 additions & 8 deletions src/services/code-index/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,14 @@ export class CodeIndexManager {
private _cacheManager: CacheManager | undefined

public static getInstance(context: vscode.ExtensionContext): CodeIndexManager | undefined {
// Use first workspace folder consistently
const workspaceFolders = vscode.workspace.workspaceFolders
if (!workspaceFolders || workspaceFolders.length === 0) {
// Get the workspace path based on the active editor or current context
const workspacePath = getWorkspacePath()
if (!workspacePath) {
return undefined
}

// Always use the first workspace folder for consistency across all indexing operations.
// This ensures that the same workspace context is used throughout the indexing pipeline,
// preventing path resolution errors in multi-workspace scenarios.
const workspacePath = workspaceFolders[0].uri.fsPath

// Use the workspace folder of the active editor to support multi-folder workspaces.
// This ensures that the codebase index corresponds to the folder the user is currently working in.
if (!CodeIndexManager.instances.has(workspacePath)) {
CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context))
}
Expand Down