document syntax highlighting

This commit is contained in:
Matt Parker
2025-08-18 18:39:06 +10:00
parent ef5a4c4a64
commit a2dce7ff17
5 changed files with 103 additions and 12 deletions

View File

@@ -2,6 +2,7 @@
using System.Diagnostics;
using Ardalis.GuardClauses;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
@@ -143,6 +144,23 @@ public static class RoslynAnalysis
return diagnostics;
}
public static async Task<IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)>> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel)
{
await _solutionLoadedTcs.Task;
var cancellationToken = CancellationToken.None;
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath);
var document = project.Documents.Single(s => s.FilePath == fileModel.Path);
Guard.Against.Null(document, nameof(document));
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken);
var root = await syntaxTree!.GetRootAsync(cancellationToken);
var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, root.FullSpan, cancellationToken);
var result = classifiedSpans.Select(s => (syntaxTree.GetMappedLineSpan(s.TextSpan), s));
return result;
}
public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Diagnostic diagnostic)
{
var cancellationToken = CancellationToken.None;

View File

@@ -1,30 +1,87 @@
using System.Linq;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
namespace SharpIDE.Godot;
public partial class CustomHighlighter : SyntaxHighlighter
{
public IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)> ClassifiedSpans = [];
public override Dictionary _GetLineSyntaxHighlighting(int line)
{
var highlights = MapClassifiedSpansToHighlights(line);
return highlights;
}
private static readonly StringName ColorStringName = "color";
private Dictionary MapClassifiedSpansToHighlights(int line)
{
var highlights = new Dictionary();
var text = GetTextEdit().GetLine(line);
var regex = new Regex(@"\bTODO\b");
var matches = regex.Matches(text);
foreach (Match match in matches)
foreach (var (fileSpan, classifiedSpan) in ClassifiedSpans)
{
highlights[match.Index] = new Dictionary
// Only take spans on the requested line
if (fileSpan.StartLinePosition.Line != line)
continue;
if (classifiedSpan.TextSpan.Length == 0)
continue; // Skip empty spans
// Column index of the first character in this span
int columnIndex = fileSpan.StartLinePosition.Character;
// Build the highlight entry
var highlightInfo = new Dictionary
{
{ "color", new Color(1, 0, 0) }, // red
{ "underline", true }, // not implemented
{ "length", match.Length } // not implemented
{ ColorStringName, GetColorForClassification(classifiedSpan.ClassificationType) }
};
highlights[columnIndex] = highlightInfo;
}
return highlights;
}
private Color GetColorForClassification(string classificationType)
{
return classificationType switch
{
// Keywords
"keyword" => new Color("569cd6"),
"keyword - control" => new Color("569cd6"),
"preprocessor keyword" => new Color("569cd6"),
// Literals & comments
"string" => new Color("d69d85"),
"comment" => new Color("57a64a"),
"number" => new Color("b5cea8"),
// Types (User Types)
"class name" => new Color("4ec9b0"),
"struct name" => new Color("4ec9b0"),
"interface name" => new Color("b8d7a3"),
"namespace name" => new Color("dcdcdc"),
// Identifiers & members
"identifier" => new Color("dcdcdc"),
"method name" => new Color("dcdcaa"),
"extension method name" => new Color("dcdcaa"),
"property name" => new Color("dcdcdc"),
"static symbol" => new Color("dcdcdc"),
"parameter name" => new Color("9cdcfe"),
"local name" => new Color("9cdcfe"),
// Punctuation & operators
"operator" => new Color("dcdcdc"),
"punctuation" => new Color("dcdcdc"),
// Misc
"excluded code" => new Color("a9a9a9"),
_ => new Color("dcdcdc")
};
}
}

View File

@@ -37,8 +37,11 @@ public partial class IdeRoot : Control
var diFile = infraProject.Files.Single(s => s.Name == "DependencyInjection.cs");
var fileContents = await File.ReadAllTextAsync(diFile.Path);
_sharpIdeCodeEdit.SetText(fileContents);
var syntaxHighlighting = await RoslynAnalysis.GetDocumentSyntaxHighlighting(diFile);
_sharpIdeCodeEdit.ProvideSyntaxHighlighting(syntaxHighlighting);
var diagnostics = await RoslynAnalysis.GetDocumentDiagnostics(diFile);
_sharpIdeCodeEdit.ProvideDiagnostics(diagnostics);
}
catch (Exception e)
{

View File

@@ -51,7 +51,10 @@ layout_mode = 2
[node name="SharpIdeCodeEdit" type="CodeEdit" parent="VBoxContainer/HBoxContainer/HSplitContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/current_line_color = Color(0.0588235, 0.0588235, 0.0588235, 1)
theme_override_colors/background_color = Color(0.117647, 0.117647, 0.117647, 1)
theme_override_fonts/font = ExtResource("2_rk34b")
theme_override_font_sizes/font_size = 18
highlight_current_line = true
gutters_draw_line_numbers = true
code_completion_enabled = true

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Godot;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
namespace SharpIDE.Godot;
@@ -12,6 +14,8 @@ public partial class SharpIdeCodeEdit : CodeEdit
private int _currentLine;
private int _selectionStartCol;
private int _selectionEndCol;
private CustomHighlighter _syntaxHighlighter = new();
private ImmutableArray<Diagnostic> _diagnostics = [];
@@ -27,7 +31,7 @@ public partial class SharpIdeCodeEdit : CodeEdit
_currentLine = GetCaretLine();
GD.Print($"Selection changed to line {_currentLine}, start {_selectionStartCol}, end {_selectionEndCol}");
};
this.SyntaxHighlighter = new CustomHighlighter();
this.SyntaxHighlighter = _syntaxHighlighter;
}
public void UnderlineRange(int line, int caretStartCol, int caretEndCol, Color color, float thickness = 1.5f)
@@ -85,6 +89,12 @@ public partial class SharpIdeCodeEdit : CodeEdit
{
_diagnostics = diagnostics;
}
public void ProvideSyntaxHighlighting(IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)> classifiedSpans)
{
_syntaxHighlighter.ClassifiedSpans = classifiedSpans;
_syntaxHighlighter.UpdateCache(); // not sure if correct
QueueRedraw(); // TODO: Not working
}
private void OnCodeFixesRequested()
{