syntax highlighting refactor

This commit is contained in:
Matt Parker
2025-10-15 21:20:51 +10:00
parent 8488e47a87
commit 21b1bd755a
2 changed files with 20 additions and 24 deletions

View File

@@ -1,6 +1,7 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Composition.Hosting; using System.Composition.Hosting;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@@ -439,6 +440,7 @@ public static class RoslynAnalysis
return sharpIdeRazorSpans; return sharpIdeRazorSpans;
} }
// This is expensive for files that have just been updated, making it suboptimal for real-time highlighting
public static async Task<IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)>> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel, CancellationToken cancellationToken = default) public static async Task<IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)>> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel, CancellationToken cancellationToken = default)
{ {
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetDocumentSyntaxHighlighting)}"); 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) 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.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);
var sourceText = SourceText.From(newContent); var sourceText = SourceText.From(newContent, Encoding.UTF8);
var document = fileModel switch var document = fileModel switch
{ {
{ IsRazorFile: true } => project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path), { IsRazorFile: true } => project.AdditionalDocuments.Single(s => s.FilePath == fileModel.Path),

View File

@@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using SharpIDE.Application;
using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Debugging;
using SharpIDE.Application.Features.SolutionDiscovery; 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}"); // 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() 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; _currentFile.IsDirty.Value = true;
await Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text); await Singletons.FileManager.UpdateFileTextInMemory(_currentFile, Text);
_ = Task.GodotRun(async () => var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
{ var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting);
var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); __?.Dispose();
var diagnostics = RoslynAnalysis.GetDocumentDiagnostics(_currentFile); _ = Task.GodotRun(async () => SetDiagnosticsModel(await 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;
});
} }
// TODO: This is now significantly slower, invoke -> text updated in editor // TODO: This is now significantly slower, invoke -> text updated in editor
@@ -300,7 +295,7 @@ public partial class SharpIdeCodeEdit : CodeEdit
_fileChangingSuppressBreakpointToggleEvent = false; _fileChangingSuppressBreakpointToggleEvent = false;
}); });
await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, setTextTask); // Text must be set before setting syntax highlighting 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); SetDiagnosticsModel(await diagnostics);
} }
@@ -408,18 +403,16 @@ public partial class SharpIdeCodeEdit : CodeEdit
private void SetDiagnosticsModel(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics) private void SetDiagnosticsModel(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics)
{ {
_diagnostics = diagnostics; _diagnostics = diagnostics;
Callable.From(QueueRedraw).CallDeferred();
} }
private void SetSyntaxHighlightingModel(IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)> classifiedSpans, IEnumerable<SharpIdeRazorClassifiedSpan> razorClassifiedSpans) private void SetSyntaxHighlightingModel(IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)> classifiedSpans, IEnumerable<SharpIdeRazorClassifiedSpan> razorClassifiedSpans)
{ {
_syntaxHighlighter.SetHighlightingData(classifiedSpans, razorClassifiedSpans); _syntaxHighlighter.SetHighlightingData(classifiedSpans, razorClassifiedSpans);
Callable.From(() => //_syntaxHighlighter.ClearHighlightingCache();
{ _syntaxHighlighter.UpdateCache();
_syntaxHighlighter.ClearHighlightingCache(); SyntaxHighlighter = null;
//_syntaxHighlighter.UpdateCache(); SyntaxHighlighter = _syntaxHighlighter; // Reassign to trigger redraw
SyntaxHighlighter = null;
SyntaxHighlighter = _syntaxHighlighter; // Reassign to trigger redraw
}).CallDeferred();
} }
private void OnCodeFixesRequested() private void OnCodeFixesRequested()