diff --git a/apps/desktop/src/components/EditorArea.tsx b/apps/desktop/src/components/EditorArea.tsx index 414a5b4d..d2eff223 100644 --- a/apps/desktop/src/components/EditorArea.tsx +++ b/apps/desktop/src/components/EditorArea.tsx @@ -106,37 +106,29 @@ const EditorArea: React.FC = () => { return () => resizeObserver.disconnect(); }, [openFiles.length]); - // Memory: Clean up Monaco models when files are closed + // Memory: Clean up Monaco models when files are closed. + // Normalize separators so Windows backslash paths match Monaco's forward-slash URIs, + // and only case-fold on Windows/UNC paths to preserve case-sensitivity on Linux. + const openPathKeys = openFiles.map(f => f.path).join("\n"); React.useEffect(() => { if (!monacoRef.current) return; - const monaco = monacoRef.current; - const openPaths = new Set(openFiles.map(f => f.path)); - - // Get all current models in Monaco - const models = monaco.editor.getModels(); - - models.forEach((model: editor.ITextModel) => { - const modelPath = model.uri.toString(); - // If the model path (uri) is not in our openFiles list, dispose it - // Note: Monaco URIs usually look like file:///path/to/file or inmemory://path - // We need to match it against our path. - - const isInternal = modelPath.startsWith("inmemory://"); - if (isInternal) return; // Don't dispose internal models - - // Check if this model corresponds to any open file - const isStillOpen = openFiles.some(f => { - // Handle both absolute paths and URI formats - return f.path === model.uri.fsPath || f.path === model.uri.path || modelPath.endsWith(f.path); - }); - - if (!isStillOpen) { - console.log(`[EditorArea] Disposing unused model: ${modelPath}`); + const normalize = (p: string) => { + const slashed = p.replace(/\\/g, "/"); + const isWindowsPath = /^[A-Za-z]:\//.test(slashed) || slashed.startsWith("//"); + return isWindowsPath ? slashed.toLowerCase() : slashed; + }; + const openPaths = new Set( + openPathKeys.split("\n").filter(Boolean).map(normalize) + ); + + monacoRef.current.editor.getModels().forEach((model: editor.ITextModel) => { + if (model.uri.scheme === "inmemory") return; + if (!openPaths.has(normalize(model.uri.fsPath))) { model.dispose(); } }); - }, [openFiles, currentFile]); + }, [openPathKeys]); const debounceTimer = useRef(null);