more file operations

This commit is contained in:
Matt Parker
2025-10-20 19:06:47 +10:00
parent 5eef6424f8
commit 36a2acbdf7
3 changed files with 91 additions and 6 deletions

View File

@@ -749,6 +749,17 @@ public class RoslynAnalysis
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath);
var existingDocument = fileModel switch
{
{ IsRazorFile: true } => project.AdditionalDocuments.SingleOrDefault(s => s.FilePath == fileModel.Path),
{ IsCsharpFile: true } => project.Documents.SingleOrDefault(s => s.FilePath == fileModel.Path),
_ => throw new InvalidOperationException("AddDocument failed: File is not a workspace file")
};
if (existingDocument is not null)
{
throw new InvalidOperationException($"AddDocument failed: Document '{fileModel.Path}' already exists in workspace");
}
var sourceText = SourceText.From(content, Encoding.UTF8);
var newSolution = fileModel switch
@@ -760,4 +771,29 @@ public class RoslynAnalysis
_workspace.TryApplyChanges(newSolution);
}
public async Task RemoveDocument(SharpIdeFile fileModel)
{
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(AddDocument)}");
await _solutionLoadedTcs.Task;
Guard.Against.Null(fileModel, nameof(fileModel));
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath);
var document = fileModel switch
{
{ IsRazorFile: true } => project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path),
{ IsCsharpFile: true } => project.Documents.Single(s => s.FilePath == fileModel.Path),
_ => throw new InvalidOperationException("UpdateDocument failed: File is not in workspace")
};
var newSolution = fileModel switch
{
{ IsRazorFile: true } => _workspace.CurrentSolution.RemoveAdditionalDocument(document.Id),
{ IsCsharpFile: true } => _workspace.CurrentSolution.RemoveDocument(document.Id),
_ => throw new InvalidOperationException("AddDocument failed: File is not in workspace")
};
_workspace.TryApplyChanges(newSolution);
}
}

View File

@@ -32,6 +32,14 @@ public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileMa
// TODO: handle csproj added
}
public async Task SharpIdeFileRemoved(SharpIdeFile file)
{
if (file.IsRoslynWorkspaceFile)
{
await HandleWorkspaceFileRemoved(file);
}
}
// All file changes should go via this service
public async Task SharpIdeFileChanged(SharpIdeFile file, string newContents, FileChangeType changeType)
{
@@ -106,4 +114,15 @@ public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileMa
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
}
private async Task HandleWorkspaceFileRemoved(SharpIdeFile file)
{
var newCts = new CancellationTokenSource();
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
await oldCts.CancelAsync();
oldCts.Dispose();
await _roslynAnalysis.RemoveDocument(file);
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
}
}

View File

@@ -1,21 +1,27 @@
using SharpIDE.Application.Features.SolutionDiscovery;
using System.Collections.Concurrent;
using Microsoft.CodeAnalysis;
using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
namespace SharpIDE.Application.Features.FileWatching;
/// Does not do any file system operations, only modifies the in-memory solution model
public class SharpIdeSolutionModificationService
public class SharpIdeSolutionModificationService(FileChangedService fileChangedService)
{
private readonly FileChangedService _fileChangedService = fileChangedService;
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
/// The directory must already exist on disk
public async Task<SharpIdeFolder> AddDirectory(SharpIdeFolder parentFolder, string directoryName)
{
// Passing [] to allFiles and allFolders, as we assume that a brand new folder has no subfolders or files yet
var addedDirectoryPath = Path.Combine(parentFolder.Path, directoryName);
var sharpIdeFolder = new SharpIdeFolder(new DirectoryInfo(addedDirectoryPath), parentFolder, [], []);
var allFiles = new ConcurrentBag<SharpIdeFile>();
var allFolders = new ConcurrentBag<SharpIdeFolder>();
var sharpIdeFolder = new SharpIdeFolder(new DirectoryInfo(addedDirectoryPath), parentFolder, allFiles, allFolders);
parentFolder.Folders.Add(sharpIdeFolder);
SolutionModel.AllFolders.Add(sharpIdeFolder);
SolutionModel.AllFolders.AddRange((IEnumerable<SharpIdeFolder>)[sharpIdeFolder, ..allFolders]);
SolutionModel.AllFiles.AddRange(allFiles);
return sharpIdeFolder;
}
@@ -23,6 +29,30 @@ public class SharpIdeSolutionModificationService
{
var parentFolderOrProject = (IFolderOrProject)folder.Parent;
parentFolderOrProject.Folders.Remove(folder);
SolutionModel.AllFolders.Remove(folder);
// Also remove all child files and folders from SolutionModel.AllFiles and AllFolders
var foldersToRemove = new List<SharpIdeFolder>();
var stack = new Stack<SharpIdeFolder>();
stack.Push(folder);
while (stack.Count > 0)
{
var current = stack.Pop();
foldersToRemove.Add(current);
foreach (var subfolder in current.Folders)
{
stack.Push(subfolder);
}
}
var filesToRemove = foldersToRemove.SelectMany(f => f.Files).ToList();
SolutionModel.AllFiles.RemoveRange(filesToRemove);
SolutionModel.AllFolders.RemoveRange(foldersToRemove);
foreach (var file in filesToRemove)
{
await _fileChangedService.SharpIdeFileRemoved(file);
}
}
}