Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | // src/workspace/WorkspaceManager.ts import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { CacheManager } from '../cache/CacheManager'; import { StaticAstIndexer } from '../indexer/StaticAstIndexer'; import { RuntimeExtractor } from '../indexer/RuntimeExtractor'; import { IdeJsonParser } from '../ide-json/IdeJsonParser'; import type { ExtensionConfig } from '../types'; // ─── Per-Folder Workspace Context ───────────────────────────────────────── export interface WorkspaceContext { folder: vscode.WorkspaceFolder; cache: CacheManager; astIndexer: StaticAstIndexer; runtimeExtractor: RuntimeExtractor; ideJsonParser: IdeJsonParser; } // ─── Workspace Manager ──────────────────────────────────────────────────── /** * Owns one WorkspaceContext per workspace folder. * This prevents cache bleed across distinct Laravel projects * in multi-root workspace sessions. */ export class WorkspaceManager implements vscode.Disposable { private readonly contexts = new Map<string, WorkspaceContext>(); private readonly disposables: vscode.Disposable[] = []; constructor(private readonly getConfig: () => ExtensionConfig) {} // ─── Initialization ─────────────────────────────────────────────────── async initialize(): Promise<void> { const folders = vscode.workspace.workspaceFolders ?? []; for (const folder of folders) { if (this.isLaravelWorkspace(folder)) { await this.addFolder(folder); } } // React to workspace folder changes this.disposables.push( vscode.workspace.onDidChangeWorkspaceFolders(async (e) => { for (const folder of e.added) { if (this.isLaravelWorkspace(folder)) { await this.addFolder(folder); } } for (const folder of e.removed) { this.removeFolder(folder); } }) ); } // ─── Context Access ─────────────────────────────────────────────────── /** * Returns the WorkspaceContext for the given document URI, * falling back to the first available context. */ getContextForDocument(uri: vscode.Uri): WorkspaceContext | undefined { const folder = vscode.workspace.getWorkspaceFolder(uri); if (folder) { return this.contexts.get(folder.uri.fsPath); } // Fallback: return first context return this.contexts.values().next().value; } getFirstContext(): WorkspaceContext | undefined { return this.contexts.values().next().value; } getAllContexts(): WorkspaceContext[] { return Array.from(this.contexts.values()); } // ─── Reindex ────────────────────────────────────────────────────────── async reindexAll(): Promise<void> { const jobs = Array.from(this.contexts.values()).map(async (ctx) => { await ctx.astIndexer.indexWorkspace(); await ctx.runtimeExtractor.extract(); }); await Promise.all(jobs); } // ─── Folder Lifecycle ───────────────────────────────────────────────── private async addFolder(folder: vscode.WorkspaceFolder): Promise<void> { const root = folder.uri.fsPath; if (this.contexts.has(root)) { return; } const config = this.getConfig(); console.log(`[Maestro] Initializing workspace context for: ${root}`); const cache = new CacheManager(root, config); const astIndexer = new StaticAstIndexer(root, cache, config); const runtimeExtractor = new RuntimeExtractor(root, cache, config); const ideJsonParser = new IdeJsonParser(root, cache); const ctx: WorkspaceContext = { folder, cache, astIndexer, runtimeExtractor, ideJsonParser, }; this.contexts.set(root, ctx); // Kick off async indexing (non-blocking) void (async () => { await astIndexer.indexWorkspace(); await runtimeExtractor.extract(); await ideJsonParser.initialize(); })(); } private removeFolder(folder: vscode.WorkspaceFolder): void { const root = folder.uri.fsPath; const ctx = this.contexts.get(root); if (!ctx) { return; } ctx.cache.dispose(); ctx.astIndexer.dispose(); ctx.runtimeExtractor.dispose(); ctx.ideJsonParser.dispose(); this.contexts.delete(root); console.log(`[Maestro] Removed workspace context for: ${root}`); } // ─── Laravel Detection ──────────────────────────────────────────────── private isLaravelWorkspace(folder: vscode.WorkspaceFolder): boolean { const artisanPath = path.join(folder.uri.fsPath, 'artisan'); return fs.existsSync(artisanPath); } // ─── Lifecycle ──────────────────────────────────────────────────────── dispose(): void { for (const ctx of this.contexts.values()) { ctx.cache.dispose(); ctx.astIndexer.dispose(); ctx.runtimeExtractor.dispose(); ctx.ideJsonParser.dispose(); } this.contexts.clear(); for (const d of this.disposables) { d.dispose(); } } } |