diff --git a/src/SharpIDE.Application/Features/Analysis/CodeActionService.cs b/src/SharpIDE.Application/Features/Analysis/CodeActionService.cs new file mode 100644 index 0000000..b8cfbb6 --- /dev/null +++ b/src/SharpIDE.Application/Features/Analysis/CodeActionService.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis.CodeActions; +using SharpIDE.Application.Features.FileWatching; + +namespace SharpIDE.Application.Features.Analysis; + +public class CodeActionService(RoslynAnalysis roslynAnalysis, FileChangedService fileChangedService) +{ + private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis; + private readonly FileChangedService _fileChangedService = fileChangedService; + + public async Task ApplyCodeAction(CodeAction codeAction) + { + var affectedFiles = await _roslynAnalysis.GetCodeActionApplyChanges(codeAction); + foreach (var (affectedFile, updatedText) in affectedFiles) + { + await _fileChangedService.SharpIdeFileChanged(affectedFile, updatedText, FileChangeType.CodeActionChange); + } + } +} diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index b9f3681..32fd5d9 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -296,6 +296,7 @@ public class RoslynAnalysis { using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnosticsForFile)}"); await _solutionLoadedTcs.Task; + if (sharpIdeFile.IsRoslynWorkspaceFile is false) return []; var cancellationToken = CancellationToken.None; var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)sharpIdeFile).GetNearestProjectNode()!.FilePath); var compilation = await project.GetCompilationAsync(cancellationToken); @@ -316,6 +317,7 @@ public class RoslynAnalysis if (fileModel.IsRoslynWorkspaceFile is false) return []; using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetDocumentDiagnostics)}"); await _solutionLoadedTcs.Task; + if (fileModel.IsRoslynWorkspaceFile is false) return []; var document = await GetDocumentForSharpIdeFile(fileModel); Guard.Against.Null(document, nameof(document)); @@ -575,14 +577,15 @@ public class RoslynAnalysis return completions; } - /// Returns the list of files modified by applying the code action - public async Task> ApplyCodeActionAsync(CodeAction codeAction) + /// Returns the list of files that would be modified by applying the code action. Does not apply the changes to the workspace sln + public async Task> GetCodeActionApplyChanges(CodeAction codeAction) { - using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(ApplyCodeActionAsync)}"); + using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeActionApplyChanges)}"); await _solutionLoadedTcs.Task; var cancellationToken = CancellationToken.None; var operations = await codeAction.GetOperationsAsync(cancellationToken); var changedDocumentIds = new List(); + var originalSolution = _workspace!.CurrentSolution; foreach (var operation in operations) { if (operation is ApplyChangesOperation applyChangesOperation) @@ -616,6 +619,8 @@ public class RoslynAnalysis }) .ToListAsync(cancellationToken); + _workspace.TryApplyChanges(originalSolution); + return changedFilesWithText; } diff --git a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs index 7bef13d..af547f9 100644 --- a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs +++ b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs @@ -14,7 +14,6 @@ public class GlobalEvents public EventWrapper ProjectStartedRunning { get; } = new(_ => Task.CompletedTask); public EventWrapper ProjectStoppedRunning { get; } = new(_ => Task.CompletedTask); public EventWrapper DebuggerExecutionStopped { get; } = new(_ => Task.CompletedTask); - public EventWrapper IdeFileSavedToDisk { get; } = new(_ => Task.CompletedTask); /// A document changed, project was reloaded etc. Document changes include unsaved changes in the IDE. public EventWrapper SolutionAltered { get; } = new(() => Task.CompletedTask); diff --git a/src/SharpIDE.Application/Features/FilePersistence/IdeOpenTabsFileManager.cs b/src/SharpIDE.Application/Features/FilePersistence/IdeOpenTabsFileManager.cs index 2b6dcc5..02d6c27 100644 --- a/src/SharpIDE.Application/Features/FilePersistence/IdeOpenTabsFileManager.cs +++ b/src/SharpIDE.Application/Features/FilePersistence/IdeOpenTabsFileManager.cs @@ -69,7 +69,6 @@ public class IdeOpenTabsFileManager(RoslynAnalysis roslynAnalysis) var text = await GetFileTextAsync(file); await WriteAllText(file, text); file.IsDirty.Value = false; - GlobalEvents.Instance.IdeFileSavedToDisk.InvokeParallelFireAndForget(file); } public async Task UpdateInMemoryIfOpenAndSaveAsync(SharpIdeFile file, string newText) diff --git a/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs b/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs new file mode 100644 index 0000000..d25839d --- /dev/null +++ b/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs @@ -0,0 +1,80 @@ +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 +} + +public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileManager openTabsFileManager) +{ + private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis; + private readonly IdeOpenTabsFileManager _openTabsFileManager = openTabsFileManager; + + public SharpIdeSolutionModel SolutionModel { get; set; } = null!; + + // All file changes should go via this service + public async Task SharpIdeFileChanged(SharpIdeFile file, string newContents, FileChangeType changeType) + { + 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(); + } + else if (changeType is FileChangeType.CodeActionChange) + { + // update in memory, tabs and save to disk + await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents); + file.FileContentsChangedExternally.InvokeParallelFireAndForget(); + } + 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 async Task HandleCsprojChanged(SharpIdeFile file) + { + var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path); + if (project is null) return; + await ProjectEvaluation.ReloadProject(file.Path); + await _roslynAnalysis.ReloadProject(project); + GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget(); + await _roslynAnalysis.UpdateSolutionDiagnostics(); + } + + private async Task HandleWorkspaceFileChanged(SharpIdeFile file, string newContents) + { + await _roslynAnalysis.UpdateDocument(file, newContents); + GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget(); + await _roslynAnalysis.UpdateSolutionDiagnostics(); + } +} diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs index c2707bb..da1ab4d 100644 --- a/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs +++ b/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs @@ -1,15 +1,15 @@ -using SharpIDE.Application.Features.Analysis; -using SharpIDE.Application.Features.Evaluation; -using SharpIDE.Application.Features.Events; +using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.FileWatching; public class IdeFileExternalChangeHandler { + private readonly FileChangedService _fileChangedService; public SharpIdeSolutionModel SolutionModel { get; set; } = null!; - public IdeFileExternalChangeHandler() + public IdeFileExternalChangeHandler(FileChangedService fileChangedService) { + _fileChangedService = fileChangedService; GlobalEvents.Instance.FileSystemWatcherInternal.FileChanged.Subscribe(OnFileChanged); } @@ -31,8 +31,7 @@ public class IdeFileExternalChangeHandler var file = SolutionModel.AllFiles.SingleOrDefault(f => f.Path == filePath); if (file is not null) { - await file.FileContentsChangedExternallyFromDisk.InvokeParallelAsync(); - await GlobalEvents.Instance.IdeFileSavedToDisk.InvokeParallelAsync(file); + await _fileChangedService.SharpIdeFileChanged(file, await File.ReadAllTextAsync(file.Path), FileChangeType.ExternalChange); } } } diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileSavedToDiskHandler.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileSavedToDiskHandler.cs deleted file mode 100644 index bc8ceda..0000000 --- a/src/SharpIDE.Application/Features/FileWatching/IdeFileSavedToDiskHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -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 class IdeFileSavedToDiskHandler -{ - private readonly IdeOpenTabsFileManager _openTabsFileManager; - private readonly RoslynAnalysis _roslynAnalysis; - public SharpIdeSolutionModel SolutionModel { get; set; } = null!; - - public IdeFileSavedToDiskHandler(IdeOpenTabsFileManager openTabsFileManager, RoslynAnalysis roslynAnalysis) - { - _openTabsFileManager = openTabsFileManager; - _roslynAnalysis = roslynAnalysis; - GlobalEvents.Instance.IdeFileSavedToDisk.Subscribe(HandleIdeFileChanged); - } - - private async Task HandleIdeFileChanged(SharpIdeFile file) - { - if (file.IsCsprojFile) - { - await HandleCsprojChanged(file); - } - else if (file.IsRoslynWorkspaceFile) - { - await HandleWorkspaceFileChanged(file); - } - } - - private async Task HandleCsprojChanged(SharpIdeFile file) - { - var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path); - if (project is null) return; - await ProjectEvaluation.ReloadProject(file.Path); - await _roslynAnalysis.ReloadProject(project); - await _roslynAnalysis.UpdateSolutionDiagnostics(); - } - - private async Task HandleWorkspaceFileChanged(SharpIdeFile file) - { - // TODO: Don't reload from disk if we raised the change event ourselves (e.g. save from IDE). Cleanup this whole disaster - var wasOpenAndUpdated = await _openTabsFileManager.ReloadFileFromDiskIfOpenInEditor(file); - if (file.IsRoslynWorkspaceFile) - { - var fileText = wasOpenAndUpdated ? - await _openTabsFileManager.GetFileTextAsync(file) : - await File.ReadAllTextAsync(file.Path); - await _roslynAnalysis.UpdateDocument(file, fileText); - GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget(); - } - await _roslynAnalysis.UpdateSolutionDiagnostics(); - } -} diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs index afeb701..4e7efec 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs @@ -19,7 +19,6 @@ public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode public required ReactiveProperty IsDirty { get; init; } public required bool SuppressDiskChangeEvents { get; set; } // probably has concurrency issues public required DateTimeOffset? LastIdeWriteTime { get; set; } - public EventWrapper FileContentsChangedExternallyFromDisk { get; } = new(() => Task.CompletedTask); // Refactor to global event - this currently doesn't handle updating un-opened files public EventWrapper FileContentsChangedExternally { get; } = new(() => Task.CompletedTask); [SetsRequiredMembers] diff --git a/src/SharpIDE.Godot/DiAutoload.cs b/src/SharpIDE.Godot/DiAutoload.cs index 8ef9f88..8bb9d19 100644 --- a/src/SharpIDE.Godot/DiAutoload.cs +++ b/src/SharpIDE.Godot/DiAutoload.cs @@ -25,7 +25,8 @@ public partial class DiAutoload : Node services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index b48433d..029f5ed 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -10,6 +10,7 @@ using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.FilePersistence; +using SharpIDE.Application.Features.FileWatching; using SharpIDE.Application.Features.Run; using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; @@ -44,6 +45,8 @@ public partial class SharpIdeCodeEdit : CodeEdit [Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!; [Inject] private readonly RunService _runService = null!; [Inject] private readonly RoslynAnalysis _roslynAnalysis = null!; + [Inject] private readonly CodeActionService _codeActionService = null!; + [Inject] private readonly FileChangedService _fileChangedService = null!; public override void _Ready() { @@ -66,6 +69,10 @@ public partial class SharpIdeCodeEdit : CodeEdit { if (_currentFile is null) return; GD.Print("Solution altered, updating project diagnostics for current file"); + var documentSyntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); + var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); + await Task.WhenAll(documentSyntaxHighlighting, razorSyntaxHighlighting); + await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await documentSyntaxHighlighting, await razorSyntaxHighlighting)); var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile); await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics)); var projectDiagnostics = await _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile); @@ -108,8 +115,7 @@ public partial class SharpIdeCodeEdit : CodeEdit public override void _ExitTree() { - _currentFile?.FileContentsChangedExternallyFromDisk.Unsubscribe(OnFileChangedExternallyFromDisk); - _currentFile?.FileContentsChangedExternally.Unsubscribe(OnFileChangedExternallyInMemory); + _currentFile?.FileContentsChangedExternally.Unsubscribe(OnFileChangedExternally); } private void OnBreakpointToggled(long line) @@ -264,30 +270,14 @@ public partial class SharpIdeCodeEdit : CodeEdit // GD.Print($"Selection changed to line {_currentLine}, start {_selectionStartCol}, end {_selectionEndCol}"); } - private CancellationTokenSource _textChangedCts = new(); private void OnTextChanged() { var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}"); _ = Task.GodotRun(async () => { _currentFile.IsDirty.Value = true; - await _openTabsFileManager.UpdateFileTextInMemory(_currentFile, Text); - await _textChangedCts.CancelAsync(); // Currently the below methods throw, TODO Fix with suppress throwing, and handle - _textChangedCts.Dispose(); - _textChangedCts = new CancellationTokenSource(); - _ = Task.GodotRun(async () => - { - var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile, _textChangedCts.Token); - var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile, _textChangedCts.Token); - await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting); - await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting)); - __?.Dispose(); - }); - _ = Task.GodotRun(async () => - { - var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile, _textChangedCts.Token); - await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics)); - }); + await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeUnsavedChange); + __?.Dispose(); }); } @@ -300,31 +290,19 @@ public partial class SharpIdeCodeEdit : CodeEdit _ = Task.GodotRun(async () => { - var affectedFiles = await _roslynAnalysis.ApplyCodeActionAsync(codeAction); - // TODO: This can be more efficient - we can just update in memory and proceed with highlighting etc. Save to disk in background. - foreach (var (affectedFile, updatedText) in affectedFiles) - { - await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(affectedFile, updatedText); - affectedFile.FileContentsChangedExternally.InvokeParallelFireAndForget(); - } + await _codeActionService.ApplyCodeAction(codeAction); }); } - private async Task OnFileChangedExternallyInMemory() + private async Task OnFileChangedExternally() { var fileContents = await _openTabsFileManager.GetFileTextAsync(_currentFile); - var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); - var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); - var diagnostics = _roslynAnalysis.GetDocumentDiagnostics(_currentFile); - await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, diagnostics); Callable.From(() => { var currentCaretPosition = GetCaretPosition(); var vScroll = GetVScroll(); BeginComplexOperation(); SetText(fileContents); - SetSyntaxHighlightingModel(syntaxHighlighting.Result, razorSyntaxHighlighting.Result); - SetDiagnostics(diagnostics.Result); SetCaretLine(currentCaretPosition.line); SetCaretColumn(currentCaretPosition.col); SetVScroll(vScroll); @@ -348,8 +326,7 @@ public partial class SharpIdeCodeEdit : CodeEdit await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); // get off the UI thread _currentFile = file; var readFileTask = _openTabsFileManager.GetFileTextAsync(file); - _currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternallyInMemory); - _currentFile.FileContentsChangedExternallyFromDisk.Subscribe(OnFileChangedExternallyFromDisk); + _currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternally); var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); @@ -369,12 +346,6 @@ public partial class SharpIdeCodeEdit : CodeEdit await this.InvokeAsync(async () => SetProjectDiagnostics(await projectDiagnosticsForFile)); } - private async Task OnFileChangedExternallyFromDisk() - { - await _openTabsFileManager.ReloadFileFromDisk(_currentFile); - await OnFileChangedExternallyInMemory(); - } - public void UnderlineRange(int line, int caretStartCol, int caretEndCol, Color color, float thickness = 1.5f) { if (line < 0 || line >= GetLineCount()) @@ -429,23 +400,25 @@ public partial class SharpIdeCodeEdit : CodeEdit public override void _UnhandledKeyInput(InputEvent @event) { - if (HasFocus() is false) return; // every tab is currently listening for this input. Only respond if we have focus. Consider refactoring this _UnhandledKeyInput to CodeEditorPanel - if (@event.IsActionPressed(InputStringNames.CodeFixes)) - { - EmitSignalCodeFixesRequested(); - } - else if (@event.IsActionPressed(InputStringNames.SaveAllFiles)) + // Let each open tab respond to this event + if (@event.IsActionPressed(InputStringNames.SaveAllFiles)) { _ = Task.GodotRun(async () => { - await _openTabsFileManager.SaveAllOpenFilesAsync(); + await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeSaveToDisk); }); } + // Now we filter to only the focused tab + if (HasFocus() is false) return; + if (@event.IsActionPressed(InputStringNames.CodeFixes)) + { + EmitSignalCodeFixesRequested(); + } else if (@event.IsActionPressed(InputStringNames.SaveFile)) { _ = Task.GodotRun(async () => { - await _openTabsFileManager.SaveFileAsync(_currentFile); + await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeSaveToDisk); }); } } diff --git a/src/SharpIDE.Godot/IdeRoot.cs b/src/SharpIDE.Godot/IdeRoot.cs index ce98bdc..5239330 100644 --- a/src/SharpIDE.Godot/IdeRoot.cs +++ b/src/SharpIDE.Godot/IdeRoot.cs @@ -38,7 +38,7 @@ public partial class IdeRoot : Control private readonly PackedScene _runMenuItemScene = ResourceLoader.Load("res://Features/Run/RunMenuItem.tscn"); private TaskCompletionSource _nodeReadyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - [Inject] private readonly IdeFileSavedToDiskHandler _savedToDiskHandler = null!; + [Inject] private readonly FileChangedService _fileChangedService = null!; [Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!; [Inject] private readonly IdeFileWatcher _fileWatcher = null!; [Inject] private readonly BuildService _buildService = null!; @@ -140,7 +140,7 @@ public partial class IdeRoot : Control _searchWindow.Solution = solutionModel; _searchAllFilesWindow.Solution = solutionModel; _fileExternalChangeHandler.SolutionModel = solutionModel; - _savedToDiskHandler.SolutionModel = solutionModel; + _fileChangedService.SolutionModel = solutionModel; Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred(); _roslynAnalysis.StartSolutionAnalysis(solutionModel); _fileWatcher.StartWatching(solutionModel);