optimise updating document content in workspace

This commit is contained in:
Matt Parker
2025-10-15 20:29:27 +10:00
parent ca8acd0adf
commit 8488e47a87
3 changed files with 36 additions and 21 deletions

View File

@@ -603,10 +603,12 @@ public static class RoslynAnalysis
public static async Task<(ISymbol?, LinePositionSpan?)> LookupSymbol(SharpIdeFile fileModel, LinePosition linePosition) public static async Task<(ISymbol?, LinePositionSpan?)> LookupSymbol(SharpIdeFile fileModel, LinePosition linePosition)
{ {
await _solutionLoadedTcs.Task; await _solutionLoadedTcs.Task;
var (symbol, linePositionSpan) = var (symbol, linePositionSpan) = fileModel switch
fileModel.IsRazorFile ? await LookupSymbolInRazor(fileModel, linePosition) {
: fileModel.IsCsharpFile ? await LookupSymbolInCs(fileModel, linePosition) { IsRazorFile: true } => await LookupSymbolInRazor(fileModel, linePosition),
: (null, null); { IsCsharpFile: true } => await LookupSymbolInCs(fileModel, linePosition),
_ => (null, null)
};
return (symbol, linePositionSpan); return (symbol, linePositionSpan);
} }
@@ -680,24 +682,37 @@ public static class RoslynAnalysis
return null; 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.Null(fileModel, nameof(fileModel));
Guard.Against.NullOrEmpty(newContent, nameof(newContent)); Guard.Against.NullOrEmpty(newContent, nameof(newContent));
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath); 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); { IsRazorFile: true } => project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path),
var newSolution = _workspace.CurrentSolution.WithAdditionalDocumentText(razorDocument.Id, SourceText.From(newContent)); { IsCsharpFile: true } => project.Documents.Single(s => s.FilePath == fileModel.Path),
_workspace.TryApplyChanges(newSolution); _ => throw new InvalidOperationException("UpdateDocument failed: File is not in workspace")
} };
else
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); { IsRazorFile: true } => _workspace.CurrentSolution.WithAdditionalDocumentText(document.Id, newText),
Guard.Against.Null(document, nameof(document)); { IsCsharpFile: true } => _workspace.CurrentSolution.WithDocumentText(document.Id, newText),
var newSolution = _workspace.CurrentSolution.WithDocumentText(document.Id, SourceText.From(newContent)); _ => throw new ArgumentOutOfRangeException()
_workspace.TryApplyChanges(newSolution); };
}
_workspace.TryApplyChanges(newSolution);
} }
} }

View File

@@ -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. // 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."); 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? // Potentially should be event based?
if (file.IsRoslynWorkspaceFile) if (file.IsRoslynWorkspaceFile)
{ {
RoslynAnalysis.UpdateDocument(file, newText); await RoslynAnalysis.UpdateDocument(file, newText);
} }
} }
@@ -47,7 +47,7 @@ public class IdeFileManager
if (file.IsRoslynWorkspaceFile) if (file.IsRoslynWorkspaceFile)
{ {
var text = await textTask; var text = await textTask;
RoslynAnalysis.UpdateDocument(file, text); await RoslynAnalysis.UpdateDocument(file, text);
} }
} }
@@ -65,7 +65,7 @@ public class IdeFileManager
{ {
if (_openFiles.ContainsKey(file)) if (_openFiles.ContainsKey(file))
{ {
UpdateFileTextInMemory(file, newText); await UpdateFileTextInMemory(file, newText);
await SaveFileAsync(file); await SaveFileAsync(file);
} }
else else

View File

@@ -211,7 +211,7 @@ public partial class SharpIdeCodeEdit : CodeEdit
{ {
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
_currentFile.IsDirty.Value = true; _currentFile.IsDirty.Value = true;
Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text); await Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text);
_ = Task.GodotRun(async () => _ = Task.GodotRun(async () =>
{ {
var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);