From 8488e47a87bc35ad780bc5c542c804207fa5b78b Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:29:27 +1000 Subject: [PATCH] optimise updating document content in workspace --- .../Features/Analysis/RoslynAnalysis.cs | 47 ++++++++++++------- .../FilePersistence/IdeFileManager.cs | 8 ++-- .../Features/CodeEditor/SharpIdeCodeEdit.cs | 2 +- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index 621d4d5..c30ea94 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -603,10 +603,12 @@ public static class RoslynAnalysis public static async Task<(ISymbol?, LinePositionSpan?)> LookupSymbol(SharpIdeFile fileModel, LinePosition linePosition) { await _solutionLoadedTcs.Task; - var (symbol, linePositionSpan) = - fileModel.IsRazorFile ? await LookupSymbolInRazor(fileModel, linePosition) - : fileModel.IsCsharpFile ? await LookupSymbolInCs(fileModel, linePosition) - : (null, null); + var (symbol, linePositionSpan) = fileModel switch + { + { IsRazorFile: true } => await LookupSymbolInRazor(fileModel, linePosition), + { IsCsharpFile: true } => await LookupSymbolInCs(fileModel, linePosition), + _ => (null, null) + }; return (symbol, linePositionSpan); } @@ -680,24 +682,37 @@ public static class RoslynAnalysis return null; } - public static void UpdateDocument(SharpIdeFile fileModel, string newContent) + public static async Task UpdateDocument(SharpIdeFile fileModel, string newContent) { Guard.Against.Null(fileModel, nameof(fileModel)); Guard.Against.NullOrEmpty(newContent, nameof(newContent)); var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath); - if (fileModel.IsRazorFile) + + var sourceText = SourceText.From(newContent); + var document = fileModel switch { - var razorDocument = project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path); - var newSolution = _workspace.CurrentSolution.WithAdditionalDocumentText(razorDocument.Id, SourceText.From(newContent)); - _workspace.TryApplyChanges(newSolution); - } - else + { 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 oldText = await document.GetTextAsync(); + + // Compute minimal text changes + var changes = sourceText.GetChangeRanges(oldText); + if (changes.Count == 0) + return; // No changes, nothing to apply + + var newText = oldText.WithChanges(sourceText.GetTextChanges(oldText)); + + var newSolution = fileModel switch { - var document = project.Documents.Single(s => s.FilePath == fileModel.Path); - Guard.Against.Null(document, nameof(document)); - var newSolution = _workspace.CurrentSolution.WithDocumentText(document.Id, SourceText.From(newContent)); - _workspace.TryApplyChanges(newSolution); - } + { IsRazorFile: true } => _workspace.CurrentSolution.WithAdditionalDocumentText(document.Id, newText), + { IsCsharpFile: true } => _workspace.CurrentSolution.WithDocumentText(document.Id, newText), + _ => throw new ArgumentOutOfRangeException() + }; + + _workspace.TryApplyChanges(newSolution); } } diff --git a/src/SharpIDE.Application/Features/FilePersistence/IdeFileManager.cs b/src/SharpIDE.Application/Features/FilePersistence/IdeFileManager.cs index 77c0167..0434ce1 100644 --- a/src/SharpIDE.Application/Features/FilePersistence/IdeFileManager.cs +++ b/src/SharpIDE.Application/Features/FilePersistence/IdeFileManager.cs @@ -24,7 +24,7 @@ public class IdeFileManager } // 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) + public async Task UpdateFileTextInMemory(SharpIdeFile file, string newText) { if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory."); @@ -33,7 +33,7 @@ public class IdeFileManager // Potentially should be event based? if (file.IsRoslynWorkspaceFile) { - RoslynAnalysis.UpdateDocument(file, newText); + await RoslynAnalysis.UpdateDocument(file, newText); } } @@ -47,7 +47,7 @@ public class IdeFileManager if (file.IsRoslynWorkspaceFile) { var text = await textTask; - RoslynAnalysis.UpdateDocument(file, text); + await RoslynAnalysis.UpdateDocument(file, text); } } @@ -65,7 +65,7 @@ public class IdeFileManager { if (_openFiles.ContainsKey(file)) { - UpdateFileTextInMemory(file, newText); + await UpdateFileTextInMemory(file, newText); await SaveFileAsync(file); } else diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index fc59ebf..fd50e50 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -211,7 +211,7 @@ public partial class SharpIdeCodeEdit : CodeEdit { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); _currentFile.IsDirty.Value = true; - Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text); + await Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text); _ = Task.GodotRun(async () => { var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);