using System.Collections.Concurrent; using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.SolutionDiscovery; namespace SharpIDE.Application.Features.FilePersistence; #pragma warning disable VSTHRD011 /// Holds the in memory copies of files, and manages saving/loading them to/from disk. public class IdeFileManager { private ConcurrentDictionary>> _openFiles = new(); /// Implicitly 'opens' a file if not already open, and returns the text. public async Task GetFileTextAsync(SharpIdeFile file) { var textTaskLazy = _openFiles.GetOrAdd(file, f => { var lazy = new Lazy>(Task () => File.ReadAllTextAsync(f.Path)); return lazy; }); var textTask = textTaskLazy.Value; var text = await textTask; return text; } // Calling this assumes that the file is already open - may need to be revisited for code fixes and refactorings. I think all files involved in a multi-file fix/refactor shall just be saved to disk immediately. public void UpdateFileTextInMemory(SharpIdeFile file, string newText) { if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory."); var newLazyTask = new Lazy>(() => Task.FromResult(newText)); _openFiles[file] = newLazyTask; // Potentially should be event based? if (file.IsRoslynWorkspaceFile) { RoslynAnalysis.UpdateDocument(file, newText); } } public async Task ReloadFileFromDisk(SharpIdeFile file) { if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory."); var newTextTaskLazy = new Lazy>(() => File.ReadAllTextAsync(file.Path)); _openFiles[file] = newTextTaskLazy; var textTask = newTextTaskLazy.Value; if (file.IsRoslynWorkspaceFile) { var text = await textTask; RoslynAnalysis.UpdateDocument(file, text); } } public async Task SaveFileAsync(SharpIdeFile file) { if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory."); if (file.IsDirty.Value is false) return; var text = await GetFileTextAsync(file); await WriteAllText(file, text); file.IsDirty.Value = false; } public async Task UpdateInMemoryIfOpenAndSaveAsync(SharpIdeFile file, string newText) { if (_openFiles.ContainsKey(file)) { UpdateFileTextInMemory(file, newText); await SaveFileAsync(file); } else { await WriteAllText(file, newText); } } private static async Task WriteAllText(SharpIdeFile file, string text) { file.SuppressDiskChangeEvents.Value = true; await File.WriteAllTextAsync(file.Path, text); Console.WriteLine($"Saved file {file.Path}"); _ = Task.Delay(300).ContinueWith(_ => { Console.WriteLine($"Re-enabling disk change events for {file.Path}"); file.SuppressDiskChangeEvents.Value = false; Console.WriteLine($"Value is now {file.SuppressDiskChangeEvents.Value}"); }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); } public async Task SaveAllOpenFilesAsync() { foreach (var file in _openFiles.Keys.ToList()) { await SaveFileAsync(file); } } } #pragma warning restore VSTHRD011