177 lines
6.5 KiB
C#
177 lines
6.5 KiB
C#
using Microsoft.VisualStudio.SolutionPersistence.Model;
|
|
using SharpIDE.Application.Features.Analysis;
|
|
using SharpIDE.Application.Features.Evaluation;
|
|
using SharpIDE.Application.Features.Events;
|
|
using SharpIDE.Application.Features.FilePersistence;
|
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
|
|
|
namespace SharpIDE.Application.Features.FileWatching;
|
|
|
|
public enum FileChangeType
|
|
{
|
|
IdeSaveToDisk, // Apply to disk
|
|
IdeUnsavedChange, // Apply only in memory
|
|
ExternalChange, // Apply to disk, as well as in memory
|
|
CodeActionChange, // Apply to disk, as well as in memory
|
|
CompletionChange // Apply only in memory, as well as notify tabs of new content
|
|
}
|
|
|
|
public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileManager openTabsFileManager)
|
|
{
|
|
private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis;
|
|
private readonly IdeOpenTabsFileManager _openTabsFileManager = openTabsFileManager;
|
|
|
|
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
|
|
|
public async Task SharpIdeFileRenamed(SharpIdeFile file, string oldFilePath)
|
|
{
|
|
if (file.IsRoslynWorkspaceFile)
|
|
{
|
|
await HandleWorkspaceFileRenamed(file, oldFilePath);
|
|
}
|
|
// TODO: handle csproj moved
|
|
}
|
|
|
|
public async Task SharpIdeFileMoved(SharpIdeFile file, string oldFilePath)
|
|
{
|
|
if (file.IsRoslynWorkspaceFile)
|
|
{
|
|
await HandleWorkspaceFileMoved(file, oldFilePath);
|
|
}
|
|
// TODO: handle csproj moved
|
|
}
|
|
|
|
public async Task SharpIdeFileAdded(SharpIdeFile file, string content)
|
|
{
|
|
if (file.IsRoslynWorkspaceFile)
|
|
{
|
|
await HandleWorkspaceFileAdded(file, content);
|
|
}
|
|
// TODO: handle csproj added
|
|
}
|
|
|
|
public async Task SharpIdeFileRemoved(SharpIdeFile file)
|
|
{
|
|
await file.FileDeleted.InvokeParallelAsync();
|
|
if (file.IsRoslynWorkspaceFile)
|
|
{
|
|
await HandleWorkspaceFileRemoved(file);
|
|
}
|
|
}
|
|
|
|
// All file changes should go via this service
|
|
public async Task SharpIdeFileChanged(SharpIdeFile file, string newContents, FileChangeType changeType, SharpIdeFileLinePosition? linePosition = null)
|
|
{
|
|
if (changeType is FileChangeType.ExternalChange)
|
|
{
|
|
// Disk is already up to date
|
|
// Update any open tabs
|
|
// update in memory
|
|
await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
|
|
file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
|
|
}
|
|
else if (changeType is FileChangeType.CodeActionChange)
|
|
{
|
|
// update in memory, tabs and save to disk
|
|
await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
|
|
file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
|
|
}
|
|
else if (changeType is FileChangeType.CompletionChange)
|
|
{
|
|
// update in memory, tabs
|
|
await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
|
|
file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
|
|
}
|
|
else if (changeType is FileChangeType.IdeSaveToDisk)
|
|
{
|
|
// save to disk
|
|
// We technically don't need to update in memory here. TODO review
|
|
await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
|
|
}
|
|
else if (changeType is FileChangeType.IdeUnsavedChange)
|
|
{
|
|
// update in memory only
|
|
await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
|
|
}
|
|
var afterSaveTask = (file, changeType) switch
|
|
{
|
|
({ IsRoslynWorkspaceFile: true }, _) => HandleWorkspaceFileChanged(file, newContents),
|
|
({ IsCsprojFile: true }, FileChangeType.IdeSaveToDisk or FileChangeType.ExternalChange) => HandleCsprojChanged(file),
|
|
({ IsCsprojFile: true }, _) => Task.CompletedTask,
|
|
_ => throw new InvalidOperationException("Unknown file change type.")
|
|
};
|
|
await afterSaveTask;
|
|
}
|
|
|
|
private CancellationTokenSource _updateSolutionDiagnosticsCts = new();
|
|
private async Task HandleCsprojChanged(SharpIdeFile file)
|
|
{
|
|
var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path);
|
|
if (project is null) return;
|
|
var newCts = new CancellationTokenSource();
|
|
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
|
|
await oldCts.CancelAsync();
|
|
oldCts.Dispose();
|
|
await ProjectEvaluation.ReloadProject(file.Path);
|
|
await _roslynAnalysis.ReloadProject(project, CancellationToken.None);
|
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
|
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
|
|
}
|
|
|
|
private async Task HandleWorkspaceFileChanged(SharpIdeFile file, string newContents)
|
|
{
|
|
var newCts = new CancellationTokenSource();
|
|
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
|
|
await oldCts.CancelAsync();
|
|
oldCts.Dispose();
|
|
await _roslynAnalysis.UpdateDocument(file, newContents);
|
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
|
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
|
|
}
|
|
|
|
private async Task HandleWorkspaceFileAdded(SharpIdeFile file, string contents)
|
|
{
|
|
var newCts = new CancellationTokenSource();
|
|
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
|
|
await oldCts.CancelAsync();
|
|
oldCts.Dispose();
|
|
await _roslynAnalysis.AddDocument(file, contents);
|
|
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);
|
|
}
|
|
|
|
private async Task HandleWorkspaceFileMoved(SharpIdeFile file, string oldFilePath)
|
|
{
|
|
var newCts = new CancellationTokenSource();
|
|
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
|
|
await oldCts.CancelAsync();
|
|
oldCts.Dispose();
|
|
await _roslynAnalysis.MoveDocument(file, oldFilePath);
|
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
|
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
|
|
}
|
|
|
|
private async Task HandleWorkspaceFileRenamed(SharpIdeFile file, string oldFilePath)
|
|
{
|
|
var newCts = new CancellationTokenSource();
|
|
var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
|
|
await oldCts.CancelAsync();
|
|
oldCts.Dispose();
|
|
await _roslynAnalysis.MoveDocument(file, oldFilePath);
|
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
|
await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
|
|
}
|
|
}
|