From 21b1bd755ae4549c83ced3191b29f274877f9f91 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:20:51 +1000 Subject: [PATCH] syntax highlighting refactor --- .../Features/Analysis/RoslynAnalysis.cs | 5 ++- .../Features/CodeEditor/SharpIdeCodeEdit.cs | 39 ++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index c30ea94..172c892 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Composition.Hosting; using System.Diagnostics; +using System.Text; using Ardalis.GuardClauses; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; @@ -439,6 +440,7 @@ public static class RoslynAnalysis return sharpIdeRazorSpans; } + // This is expensive for files that have just been updated, making it suboptimal for real-time highlighting public static async Task> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel, CancellationToken cancellationToken = default) { using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetDocumentSyntaxHighlighting)}"); @@ -684,12 +686,13 @@ public static class RoslynAnalysis public static async Task UpdateDocument(SharpIdeFile fileModel, string newContent) { + using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(UpdateDocument)}"); 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); - var sourceText = SourceText.From(newContent); + var sourceText = SourceText.From(newContent, Encoding.UTF8); var document = fileModel switch { { IsRazorFile: true } => project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path), diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index fd50e50..d62e869 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Text; +using SharpIDE.Application; using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.SolutionDiscovery; @@ -207,25 +208,19 @@ public partial class SharpIdeCodeEdit : CodeEdit // GD.Print($"Selection changed to line {_currentLine}, start {_selectionStartCol}, end {_selectionEndCol}"); } + // Ideally this method completes in < 35ms, to ~ handle 28 char/s spam typing private async void OnTextChanged() { - await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}"); + // Note that we are currently on the UI thread, so be very conscious of what we do here + // If we update the documents syntax highlighting here, we won't get any flashes of incorrect highlighting, most noticeable when inserting new lines _currentFile.IsDirty.Value = true; await Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text); - _ = Task.GodotRun(async () => - { - var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); - var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); - var diagnostics = RoslynAnalysis.GetDocumentDiagnostics(_currentFile); - var slnDiagnostics = RoslynAnalysis.UpdateSolutionDiagnostics(); - await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, diagnostics); - Callable.From(() => - { - SetSyntaxHighlightingModel(syntaxHighlighting.Result, razorSyntaxHighlighting.Result); - SetDiagnosticsModel(diagnostics.Result); - }).CallDeferred(); - await slnDiagnostics; - }); + var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); + var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); + SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting); + __?.Dispose(); + _ = Task.GodotRun(async () => SetDiagnosticsModel(await RoslynAnalysis.GetDocumentDiagnostics(_currentFile))); } // TODO: This is now significantly slower, invoke -> text updated in editor @@ -300,7 +295,7 @@ public partial class SharpIdeCodeEdit : CodeEdit _fileChangingSuppressBreakpointToggleEvent = false; }); await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, setTextTask); // Text must be set before setting syntax highlighting - SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting); + await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting)); SetDiagnosticsModel(await diagnostics); } @@ -408,18 +403,16 @@ public partial class SharpIdeCodeEdit : CodeEdit private void SetDiagnosticsModel(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics) { _diagnostics = diagnostics; + Callable.From(QueueRedraw).CallDeferred(); } private void SetSyntaxHighlightingModel(IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)> classifiedSpans, IEnumerable razorClassifiedSpans) { _syntaxHighlighter.SetHighlightingData(classifiedSpans, razorClassifiedSpans); - Callable.From(() => - { - _syntaxHighlighter.ClearHighlightingCache(); - //_syntaxHighlighter.UpdateCache(); - SyntaxHighlighter = null; - SyntaxHighlighter = _syntaxHighlighter; // Reassign to trigger redraw - }).CallDeferred(); + //_syntaxHighlighter.ClearHighlightingCache(); + _syntaxHighlighter.UpdateCache(); + SyntaxHighlighter = null; + SyntaxHighlighter = _syntaxHighlighter; // Reassign to trigger redraw } private void OnCodeFixesRequested()