Skip to content
Open
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: 2 additions & 2 deletions src/__tests__/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ vi.mock("../services/mcp/McpServerManager", () => ({
},
}))

vi.mock("../services/code-index/manager", () => ({
CodeIndexManager: {
vi.mock("../services/code-index/code-index-manager-registry", () => ({
CodeIndexManagerRegistry: {
getInstance: vi.fn().mockReturnValue(null),
},
}))
Expand Down
4 changes: 2 additions & 2 deletions src/activate/__tests__/registerCommands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ vi.mock("../../core/config/importExport", () => ({
importSettingsWithFeedback: vi.fn(),
}))

vi.mock("../../services/code-index/manager", () => ({
CodeIndexManager: {
vi.mock("../../services/code-index/code-index-manager-registry", () => ({
CodeIndexManagerRegistry: {
getInstance: vi.fn(),
},
}))
Expand Down
4 changes: 2 additions & 2 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ClineProvider } from "../core/webview/ClineProvider"
import { ContextProxy } from "../core/config/ContextProxy"
import { focusPanel } from "../utils/focusPanel"
import { handleNewTask } from "./handleTask"
import { CodeIndexManager } from "../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../services/code-index/code-index-manager-registry"
import { importSettingsWithFeedback } from "../core/config/importExport"
import { MdmService } from "../services/mdm/MdmService"
import { registerRipgrepDiagnosticCommand } from "../services/ripgrep/diagnostic"
Expand Down Expand Up @@ -227,7 +227,7 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
// don't need to use that event).
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
const contextProxy = await ContextProxy.getInstance(context)
const codeIndexManager = CodeIndexManager.getInstance(context)
const codeIndexManager = CodeIndexManagerRegistry.getInstance(context)

// Get the existing MDM service instance to ensure consistent policy enforcement
let mdmService: MdmService | undefined
Expand Down
4 changes: 2 additions & 2 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { formatLanguage } from "../../shared/language"
import { isEmpty } from "../../utils/object"

import { McpHub } from "../../services/mcp/McpHub"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { SkillsManager } from "../../services/skills/SkillsManager"

import type { SystemPromptSettings } from "./types"
Expand Down Expand Up @@ -79,7 +79,7 @@ async function generatePrompt(
}
const shouldIncludeMcp = hasMcpGroup && hasMcpServers

const codeIndexManager = CodeIndexManager.getInstance(context, cwd)
const codeIndexManager = CodeIndexManagerRegistry.getInstance(context, cwd)

// Tool calling is native-only.
const effectiveProtocol = "native"
Expand Down
7 changes: 7 additions & 0 deletions src/core/task/__tests__/Task.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ vi.mock("p-wait-for", () => ({
default: vi.fn().mockImplementation(async () => Promise.resolve()),
}))

// Task tests do not exercise indexing; keep workspace resolution and its cache out of this suite.
vi.mock("../../../services/code-index/code-index-manager-registry", () => ({
CodeIndexManagerRegistry: {
getInstance: vi.fn().mockReturnValue(undefined),
},
}))

vi.mock("vscode", () => {
const mockDisposable = { dispose: vi.fn() }
const mockEventEmitter = { event: vi.fn(), fire: vi.fn() }
Expand Down
4 changes: 2 additions & 2 deletions src/core/task/build-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO
const mcpHub = provider.getMcpHub()

// Get CodeIndexManager for feature checking.
const { CodeIndexManager } = await import("../../services/code-index/manager")
const codeIndexManager = CodeIndexManager.getInstance(provider.context, cwd)
const { CodeIndexManagerRegistry } = await import("../../services/code-index/code-index-manager-registry")
const codeIndexManager = CodeIndexManagerRegistry.getInstance(provider.context, cwd)

// Build settings object for tool filtering.
const filterSettings = {
Expand Down
4 changes: 2 additions & 2 deletions src/core/tools/CodebaseSearchTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from "vscode"
import path from "path"

import { Task } from "../task/Task"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { getWorkspacePath } from "../../utils/path"
import { formatResponse } from "../prompts/responses"
import { VectorStoreSearchResult } from "../../services/code-index/interfaces"
Expand Down Expand Up @@ -57,7 +57,7 @@ export class CodebaseSearchTool extends BaseTool<"codebase_search"> {
throw new Error("Extension context is not available.")
}

const manager = CodeIndexManager.getInstance(context)
const manager = CodeIndexManagerRegistry.getInstance(context)

if (!manager) {
throw new Error("CodeIndexManager is not available.")
Expand Down
3 changes: 2 additions & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { McpServerManager } from "../../services/mcp/McpServerManager"
import { MarketplaceManager } from "../../services/marketplace"
import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
import { MdmService } from "../../services/mdm/MdmService"
import { SkillsManager } from "../../services/skills/SkillsManager"
Expand Down Expand Up @@ -3307,7 +3308,7 @@ export class ClineProvider
* @returns CodeIndexManager instance for the current workspace or the default one
*/
public getCurrentWorkspaceCodeIndexManager(): CodeIndexManager | undefined {
return CodeIndexManager.getInstance(this.context)
return CodeIndexManagerRegistry.getInstance(this.context)
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3225,7 +3225,7 @@ describe("webviewMessageHandler no-floating-promises coverage", () => {
})

it("catches auto-enabled indexing failures and posts the resulting status", async () => {
const { CodeIndexManager } = await import("../../../services/code-index/manager")
const { CodeIndexManagerRegistry } = await import("../../../services/code-index/code-index-manager-registry")
let workspaceEnabled = false
const manager = createIndexManager({
setAutoEnableDefault: vi.fn().mockImplementation(async () => {
Expand All @@ -3235,8 +3235,8 @@ describe("webviewMessageHandler no-floating-promises coverage", () => {
})
Object.defineProperty(manager, "isWorkspaceEnabled", { get: () => workspaceEnabled })
const getAllInstances = vi
.spyOn(CodeIndexManager, "getAllInstances")
.mockReturnValue([manager] as unknown as ReturnType<typeof CodeIndexManager.getAllInstances>)
.spyOn(CodeIndexManagerRegistry, "getAllInstances")
.mockReturnValue([manager] as unknown as ReturnType<typeof CodeIndexManagerRegistry.getAllInstances>)
const provider = createProvider({
getCurrentWorkspaceCodeIndexManager: vi.fn().mockReturnValue(manager),
})
Expand Down
4 changes: 2 additions & 2 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { Package } from "../../shared/package"
import { type RouterName, toRouterName } from "../../shared/api"
import { MessageEnhancer } from "./messageEnhancer"

import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { getRouterRemovalMessage, getRouterUnavailableSignInMessage } from "../config/routerRemoval"
import { experimentDefault } from "../../shared/experiments"
Expand Down Expand Up @@ -3311,7 +3311,7 @@ export const webviewMessageHandler = async (
return
}
// Capture prior state for every manager before persisting the global change
const allManagers = CodeIndexManager.getAllInstances()
const allManagers = CodeIndexManagerRegistry.getAllInstances()
const priorStates = new Map(allManagers.map((m) => [m, m.isWorkspaceEnabled]))
await manager.setAutoEnableDefault(message.bool ?? true)
// Apply stop/start to every affected manager
Expand Down
2 changes: 1 addition & 1 deletion src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1301,7 +1301,7 @@
},
"services/code-index/__tests__/manager.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 89
"count": 87
}
},
"services/code-index/__tests__/orchestrator.spec.ts": {
Expand Down
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { openAiCodexOAuthManager } from "./integrations/openai-codex/oauth"
import { kimiCodeOAuthManager } from "./integrations/kimi-code/oauth"
import { McpServerManager } from "./services/mcp/McpServerManager"
import { CodeIndexManager } from "./services/code-index/manager"
import { CodeIndexManagerRegistry } from "./services/code-index/code-index-manager-registry"
import { MdmService } from "./services/mdm/MdmService"
import { migrateSettings } from "./utils/migrateSettings"
import { autoImportSettings } from "./utils/autoImportSettings"
Expand Down Expand Up @@ -200,7 +201,7 @@ export async function activate(context: vscode.ExtensionContext) {

if (vscode.workspace.workspaceFolders) {
for (const folder of vscode.workspace.workspaceFolders) {
const manager = CodeIndexManager.getInstance(context, folder.uri.fsPath)
const manager = CodeIndexManagerRegistry.getInstance(context, folder.uri.fsPath)

if (manager) {
codeIndexManagers.push(manager)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as vscode from "vscode"
import { makeExtensionContext, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode"
import { CodeIndexManager } from "../manager"
import { CodeIndexManagerRegistry } from "../code-index-manager-registry"

vi.mock("vscode", () => ({
workspace: { workspaceFolders: undefined, getWorkspaceFolder: vi.fn() },
window: { activeTextEditor: undefined },
Uri: { file: vi.fn() },
}))

vi.mock("../manager", () => ({
CodeIndexManager: vi.fn().mockImplementation(function () {
return { dispose: vi.fn() }
}),
}))

describe("CodeIndexManagerRegistry", () => {
let context: vscode.ExtensionContext
let first: vscode.WorkspaceFolder
let second: vscode.WorkspaceFolder

beforeEach(() => {
vi.clearAllMocks()
context = makeExtensionContext()
first = { uri: makeUri("/first"), name: "first", index: 0 }
second = { uri: makeUri("/second"), name: "second", index: 1 }
Object.defineProperty(vscode.workspace, "workspaceFolders", { configurable: true, value: [first, second] })
Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: undefined })
vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(undefined)
vi.mocked(vscode.Uri.file).mockImplementation((value) => makeUri(value))
})

afterEach(() => {
CodeIndexManagerRegistry.disposeAll()
vi.restoreAllMocks()
})

it.each([{ folders: undefined }, { folders: [] }])("returns no manager with folders=$folders", ({ folders }) => {
Object.defineProperty(vscode.workspace, "workspaceFolders", { configurable: true, value: folders })
expect(CodeIndexManagerRegistry.getInstance(context)).toBeUndefined()
expect(CodeIndexManager).not.toHaveBeenCalled()
})

it("uses the first workspace when there is no active editor", () => {
CodeIndexManagerRegistry.getInstance(context)
expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context)
})

it("prefers the active editor's workspace", () => {
const editor = makeTextEditor({ document: makeTextDocument({ uri: makeUri("/second/file.ts") }) })
Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: editor })
vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(second)
CodeIndexManagerRegistry.getInstance(context)
expect(vscode.workspace.getWorkspaceFolder).toHaveBeenCalledWith(editor.document.uri)
expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context)
})

it("falls back to the first workspace for an editor outside all folders", () => {
Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() })
CodeIndexManagerRegistry.getInstance(context)
expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context)
})

it("gives an explicit path priority over the active editor", () => {
Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() })
vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(first)
CodeIndexManagerRegistry.getInstance(context, "/second")
expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context)
expect(vscode.workspace.getWorkspaceFolder).not.toHaveBeenCalled()
})

it("preserves the actual remote workspace URI", () => {
const uri = makeUri("/remote", { scheme: "vscode-remote", authority: "ssh-remote+host" })
Object.defineProperty(vscode.workspace, "workspaceFolders", {
configurable: true,
value: [{ uri, name: "remote", index: 0 }],
})
CodeIndexManagerRegistry.getInstance(context, "/remote")
expect(CodeIndexManager).toHaveBeenCalledWith("/remote", uri, context)
expect(vi.mocked(CodeIndexManager).mock.calls[0][1]).toBe(uri)
expect(vscode.Uri.file).not.toHaveBeenCalled()
})

it("constructs a file URI for an explicit path without open workspaces", () => {
Object.defineProperty(vscode.workspace, "workspaceFolders", { configurable: true, value: undefined })
const uri = makeUri("/outside folder/#name")
vi.mocked(vscode.Uri.file).mockReturnValue(uri)
CodeIndexManagerRegistry.getInstance(context, uri.fsPath)
expect(vscode.Uri.file).toHaveBeenCalledWith(uri.fsPath)
expect(CodeIndexManager).toHaveBeenCalledWith(uri.fsPath, uri, context)
})

it("reuses the same path and keeps different paths isolated", () => {
const a = CodeIndexManagerRegistry.getInstance(context, "/first")
expect(CodeIndexManagerRegistry.getInstance(makeExtensionContext(), "/first")).toBe(a)
const b = CodeIndexManagerRegistry.getInstance(context, "/second")
expect(b).not.toBe(a)
expect(CodeIndexManager).toHaveBeenCalledTimes(2)
expect(CodeIndexManagerRegistry.getAllInstances()).toEqual([a, b])
})

it("returns a snapshot that cannot mutate the cache", () => {
expect(CodeIndexManagerRegistry.getAllInstances()).toEqual([])
const manager = CodeIndexManagerRegistry.getInstance(context)
CodeIndexManagerRegistry.getAllInstances().pop()
expect(CodeIndexManagerRegistry.getAllInstances()).toEqual([manager])
})

it("disposes every manager, supports repeated cleanup and recreates instances", () => {
const a = CodeIndexManagerRegistry.getInstance(context, "/first")!
const b = CodeIndexManagerRegistry.getInstance(context, "/second")!
CodeIndexManagerRegistry.disposeAll()
CodeIndexManagerRegistry.disposeAll()
expect(a.dispose).toHaveBeenCalledTimes(1)
expect(b.dispose).toHaveBeenCalledTimes(1)
expect(CodeIndexManagerRegistry.getAllInstances()).toEqual([])
expect(CodeIndexManagerRegistry.getInstance(context, "/first")).not.toBe(a)
})
})
15 changes: 8 additions & 7 deletions src/services/code-index/__tests__/manager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CodeIndexManager } from "../manager"
import { CodeIndexManagerRegistry } from "../code-index-manager-registry"
import { CodeIndexServiceFactory } from "../service-factory"
import type { MockedClass } from "vitest"
import * as path from "path"
Expand Down Expand Up @@ -126,7 +127,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {

beforeEach(() => {
// Clear all instances before each test
CodeIndexManager.disposeAll()
CodeIndexManagerRegistry.disposeAll()

const workspaceStateStore: Record<string, any> = {}
const globalStateStore: Record<string, any> = {}
Expand Down Expand Up @@ -160,11 +161,11 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
languageModelAccessInformation: {} as any,
}

manager = CodeIndexManager.getInstance(mockContext)!
manager = CodeIndexManagerRegistry.getInstance(mockContext)!
})

afterEach(() => {
CodeIndexManager.disposeAll()
CodeIndexManagerRegistry.disposeAll()
})

describe("handleSettingsChange", () => {
Expand Down Expand Up @@ -733,7 +734,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
})

it("should store enablement per folder URI, not per window", async () => {
CodeIndexManager.disposeAll()
CodeIndexManagerRegistry.disposeAll()

const vscode = await import("vscode")

Expand Down Expand Up @@ -764,8 +765,8 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
{ uri: folderBUri, name: "folderB", index: 1 },
]

const managerA = CodeIndexManager.getInstance(sharedContext as any, folderAPath)!
const managerB = CodeIndexManager.getInstance(sharedContext as any, folderBPath)!
const managerA = CodeIndexManagerRegistry.getInstance(sharedContext, folderAPath)!
const managerB = CodeIndexManagerRegistry.getInstance(sharedContext, folderBPath)!

// Both start disabled (autoEnableDefault is false via globalState mock)
expect(managerA.isWorkspaceEnabled).toBe(false)
Expand All @@ -784,7 +785,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
expect(managerA.isWorkspaceEnabled).toBe(false)
expect(managerB.isWorkspaceEnabled).toBe(true)

CodeIndexManager.disposeAll()
CodeIndexManagerRegistry.disposeAll()
})
})

Expand Down
Loading
Loading