Skip to content

Commit d67e0e2

Browse files
author
James Mtendamema
committed
fix: URI-keyed enablement, throw AbortError in back-pressure, stopWatcher on early-return
1 parent 051f80e commit d67e0e2

5 files changed

Lines changed: 114 additions & 29 deletions

File tree

src/__mocks__/vscode.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const mockDisposable = {
1010
}
1111

1212
const mockUri = {
13-
file: (path) => ({ fsPath: path, path, scheme: "file" }),
14-
parse: (path) => ({ fsPath: path, path, scheme: "file" }),
13+
file: (path) => ({ fsPath: path, path, scheme: "file", toString: () => `file://${path}` }),
14+
parse: (path) => ({ fsPath: path, path, scheme: "file", toString: () => path }),
1515
}
1616

1717
const mockRange = class {

src/services/code-index/__tests__/manager.spec.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,45 @@ import { CodeIndexServiceFactory } from "../service-factory"
33
import type { MockedClass } from "vitest"
44
import * as path from "path"
55

6+
// Helper: create a mock vscode.Uri from an fsPath
7+
function mockUri(fsPath: string, scheme = "file") {
8+
return {
9+
fsPath,
10+
scheme,
11+
authority: "",
12+
path: fsPath,
13+
toString: (skipEncoding?: boolean) => `${scheme}://${fsPath}`,
14+
}
15+
}
16+
617
// Mock vscode module
718
vi.mock("vscode", () => {
819
const testPath = require("path")
920
const testWorkspacePath = testPath.join(testPath.sep, "test", "workspace")
1021
return {
22+
Uri: {
23+
file: (p: string) => ({
24+
fsPath: p,
25+
scheme: "file",
26+
authority: "",
27+
path: p,
28+
toString: (_skipEncoding?: boolean) => `file://${p}`,
29+
}),
30+
joinPath: vi.fn((...args: any[]) => ({ fsPath: args.join("/") })),
31+
},
1132
window: {
1233
activeTextEditor: null,
1334
},
1435
workspace: {
1536
workspaceFolders: [
1637
{
17-
uri: { fsPath: testWorkspacePath },
38+
uri: {
39+
fsPath: testWorkspacePath,
40+
scheme: "file",
41+
authority: "",
42+
path: testWorkspacePath,
43+
toString: (_skipEncoding?: boolean) => `file://${testWorkspacePath}`,
44+
},
1845
name: "test",
1946
index: 0,
2047
},
@@ -25,8 +52,9 @@ vi.mock("vscode", () => {
2552
onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
2653
dispose: vi.fn(),
2754
}),
55+
getWorkspaceFolder: vi.fn(),
2856
},
29-
RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })),
57+
RelativePattern: vi.fn().mockImplementation((base: any, pattern: any) => ({ base, pattern })),
3058
}
3159
})
3260

@@ -672,18 +700,59 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
672700
expect(manager.isWorkspaceEnabled).toBe(false)
673701
})
674702

675-
it("should store enablement per folder, not per window", async () => {
676-
await manager.setAutoEnableDefault(false)
703+
it("should store enablement per folder URI, not per window", async () => {
704+
CodeIndexManager.disposeAll()
705+
706+
const vscode = await import("vscode")
707+
708+
const folderAPath = path.join(path.sep, "test", "folderA")
709+
const folderBPath = path.join(path.sep, "test", "folderB")
710+
const folderAUri = mockUri(folderAPath)
711+
const folderBUri = mockUri(folderBPath)
712+
713+
// Both folders share the same workspaceState (same window)
714+
const sharedStore: Record<string, any> = {}
715+
const sharedContext = {
716+
...mockContext,
717+
workspaceState: {
718+
get: vi.fn((key: string, defaultValue?: any) => sharedStore[key] ?? defaultValue),
719+
update: vi.fn(async (key: string, value: any) => {
720+
sharedStore[key] = value
721+
}),
722+
} as any,
723+
globalState: {
724+
get: vi.fn((_key: string, _defaultValue?: any) => false),
725+
update: vi.fn(),
726+
} as any,
727+
}
677728

678-
// Enable indexing for the current manager's folder
679-
await manager.setWorkspaceEnabled(true)
680-
expect(manager.isWorkspaceEnabled).toBe(true)
729+
// Patch workspaceFolders to include both folders
730+
;(vscode.workspace as any).workspaceFolders = [
731+
{ uri: folderAUri, name: "folderA", index: 0 },
732+
{ uri: folderBUri, name: "folderB", index: 1 },
733+
]
734+
735+
const managerA = CodeIndexManager.getInstance(sharedContext as any, folderAPath)!
736+
const managerB = CodeIndexManager.getInstance(sharedContext as any, folderBPath)!
737+
738+
// Both start disabled (autoEnableDefault is false via globalState mock)
739+
expect(managerA.isWorkspaceEnabled).toBe(false)
740+
expect(managerB.isWorkspaceEnabled).toBe(false)
741+
742+
// Enable A only
743+
await managerA.setWorkspaceEnabled(true)
744+
745+
expect(managerA.isWorkspaceEnabled).toBe(true)
746+
expect(managerB.isWorkspaceEnabled).toBe(false)
747+
748+
// Enable B, disable A
749+
await managerB.setWorkspaceEnabled(true)
750+
await managerA.setWorkspaceEnabled(false)
681751

682-
// Create a second manager for a different folder path
683-
const otherManager = CodeIndexManager.getInstance(mockContext as any, "/other/workspace")!
752+
expect(managerA.isWorkspaceEnabled).toBe(false)
753+
expect(managerB.isWorkspaceEnabled).toBe(true)
684754

685-
// The other folder should NOT be enabled — keys are per-folder
686-
expect(otherManager.isWorkspaceEnabled).toBe(false)
755+
CodeIndexManager.disposeAll()
687756
})
688757
})
689758

src/services/code-index/manager.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,31 @@ export class CodeIndexManager {
3232
private _isRecoveringFromError = false
3333

3434
public static getInstance(context: vscode.ExtensionContext, workspacePath?: string): CodeIndexManager | undefined {
35-
// If workspacePath is not provided, try to get it from the active editor or first workspace folder
36-
if (!workspacePath) {
35+
// Resolve the workspace folder to get both fsPath and the real URI
36+
let folder: vscode.WorkspaceFolder | undefined
37+
38+
if (workspacePath) {
39+
folder = vscode.workspace.workspaceFolders?.find((f) => f.uri.fsPath === workspacePath)
40+
} else {
3741
const activeEditor = vscode.window.activeTextEditor
3842
if (activeEditor) {
39-
const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri)
40-
workspacePath = workspaceFolder?.uri.fsPath
43+
folder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri)
4144
}
42-
43-
if (!workspacePath) {
45+
if (!folder) {
4446
const workspaceFolders = vscode.workspace.workspaceFolders
4547
if (!workspaceFolders || workspaceFolders.length === 0) {
4648
return undefined
4749
}
48-
// Use the first workspace folder as fallback
49-
workspacePath = workspaceFolders[0].uri.fsPath
50+
folder = workspaceFolders[0]
5051
}
52+
workspacePath = folder.uri.fsPath
5153
}
5254

5355
if (!CodeIndexManager.instances.has(workspacePath)) {
54-
CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context))
56+
// folder may be undefined when workspacePath was provided but doesn't match
57+
// any workspace folder (e.g. cwd passed from a tool). Fall back to file:// URI.
58+
const folderUri = folder?.uri ?? vscode.Uri.file(workspacePath)
59+
CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, folderUri, context))
5560
}
5661
return CodeIndexManager.instances.get(workspacePath)!
5762
}
@@ -64,28 +69,35 @@ export class CodeIndexManager {
6469
}
6570

6671
private readonly workspacePath: string
72+
private readonly _folderUri: vscode.Uri
6773
private readonly context: vscode.ExtensionContext
6874

6975
// Private constructor for singleton pattern
70-
private constructor(workspacePath: string, context: vscode.ExtensionContext) {
76+
private constructor(workspacePath: string, folderUri: vscode.Uri, context: vscode.ExtensionContext) {
7177
this.workspacePath = workspacePath
78+
this._folderUri = folderUri
7279
this.context = context
7380
this._stateManager = new CodeIndexStateManager()
7481
}
7582

7683
// --- Public API ---
7784

85+
/**
86+
* Returns the workspaceState key for per-folder indexing enablement,
87+
* keyed by the real workspace folder URI so local/remote schemes cannot collide.
88+
*/
89+
private _workspaceEnabledKey(): string {
90+
return "codeIndexWorkspaceEnabled:" + this._folderUri.toString(true)
91+
}
92+
7893
public get isWorkspaceEnabled(): boolean {
79-
const explicit = this.context.workspaceState.get<boolean | undefined>(
80-
`codeIndexWorkspaceEnabled:${this.workspacePath}`,
81-
undefined,
82-
)
94+
const explicit = this.context.workspaceState.get<boolean | undefined>(this._workspaceEnabledKey(), undefined)
8395
if (explicit !== undefined) return explicit
8496
return this.autoEnableDefault
8597
}
8698

8799
public async setWorkspaceEnabled(enabled: boolean): Promise<void> {
88-
await this.context.workspaceState.update(`codeIndexWorkspaceEnabled:${this.workspacePath}`, enabled)
100+
await this.context.workspaceState.update(this._workspaceEnabledKey(), enabled)
89101
}
90102

91103
public get autoEnableDefault(): boolean {

src/services/code-index/orchestrator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export class CodeIndexOrchestrator {
186186

187187
if (signal.aborted) {
188188
await this.cacheManager.flush()
189+
this.stopWatcher()
189190
this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
190191
return
191192
}
@@ -246,6 +247,7 @@ export class CodeIndexOrchestrator {
246247

247248
if (signal.aborted) {
248249
await this.cacheManager.flush()
250+
this.stopWatcher()
249251
this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
250252
return
251253
}

src/services/code-index/processors/scanner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ export class DirectoryScanner implements IDirectoryScanner {
185185
if (currentBatchBlocks.length >= this.batchSegmentThreshold) {
186186
// Wait if we've reached the maximum pending batches
187187
while (pendingBatchCount >= MAX_PENDING_BATCHES) {
188-
if (signal?.aborted) break
188+
if (signal?.aborted) {
189+
throw new DOMException("Indexing aborted", "AbortError")
190+
}
189191
await Promise.race(activeBatchPromises)
190192
}
191193

0 commit comments

Comments
 (0)