syntaxNodes)
+ {
+ for (var i = 0; i < syntaxNodes.Count; i++)
+ {
+ Visit(syntaxNodes[i]);
+ }
+ }
+
+ private bool IsInRange(TextSpan span)
+ {
+ return _range.OverlapsWith(span);
+ }
+
+ public override void Visit(SyntaxNode? node)
+ {
+ if (node != null && IsInRange(node.Span))
+ {
+ base.Visit(node);
+ }
+ }
+
+ #region HTML
+
+ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
+ {
+ // Don't return anything for MarkupTextLiterals. It translates to "text" on the VS side, which is the default color anyway
+
+ // We want to return something, due to how Godot colorizes text.
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.MarkupTextLiteral);
+ }
+
+ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
+ {
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.MarkupAttributeValue);
+ }
+
+ public override void VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ Visit(node.NamePrefix);
+ AddSemanticRange(node.Name, tokenTypes.MarkupAttribute);
+ Visit(node.NameSuffix);
+ AddSemanticRange(node.EqualsToken, tokenTypes.MarkupOperator);
+
+ AddSemanticRange(node.ValuePrefix, tokenTypes.MarkupAttributeQuote);
+ Visit(node.Value);
+ AddSemanticRange(node.ValueSuffix, tokenTypes.MarkupAttributeQuote);
+ }
+
+ public override void VisitMarkupStartTag(MarkupStartTagSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ if (node.IsMarkupTransition)
+ {
+ AddSemanticRange(node, tokenTypes.RazorDirective);
+ }
+ else
+ {
+ AddSemanticRange(node.OpenAngle, tokenTypes.MarkupTagDelimiter);
+
+ if (node.Bang.IsValid(out var bang))
+ {
+ AddSemanticRange(bang, tokenTypes.RazorTransition);
+ }
+
+ AddSemanticRange(node.Name, tokenTypes.MarkupElement);
+
+ Visit(node.Attributes);
+ if (node.ForwardSlash.IsValid(out var forwardSlash))
+ {
+ AddSemanticRange(forwardSlash, tokenTypes.MarkupTagDelimiter);
+ }
+
+ AddSemanticRange(node.CloseAngle, tokenTypes.MarkupTagDelimiter);
+ }
+ }
+
+ public override void VisitMarkupEndTag(MarkupEndTagSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ if (node.IsMarkupTransition)
+ {
+ AddSemanticRange(node, tokenTypes.RazorDirective);
+ }
+ else
+ {
+ AddSemanticRange(node.OpenAngle, tokenTypes.MarkupTagDelimiter);
+
+ if (node.Bang.IsValid(out var bang))
+ {
+ AddSemanticRange(bang, tokenTypes.RazorTransition);
+ }
+
+ if (node.ForwardSlash.IsValid(out var forwardSlash))
+ {
+ AddSemanticRange(forwardSlash, tokenTypes.MarkupTagDelimiter);
+ }
+
+ AddSemanticRange(node.Name, tokenTypes.MarkupElement);
+
+ AddSemanticRange(node.CloseAngle, tokenTypes.MarkupTagDelimiter);
+ }
+ }
+
+ public override void VisitMarkupCommentBlock(MarkupCommentBlockSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.Children[0], tokenTypes.MarkupCommentPunctuation);
+
+ for (var i = 1; i < node.Children.Count - 1; i++)
+ {
+ var commentNode = node.Children[i];
+ switch (commentNode.Kind)
+ {
+ case SyntaxKind.MarkupTextLiteral:
+ AddSemanticRange(commentNode, tokenTypes.MarkupComment);
+ break;
+ default:
+ Visit(commentNode);
+ break;
+ }
+ }
+
+ AddSemanticRange(node.Children[^1], tokenTypes.MarkupCommentPunctuation);
+ }
+
+ public override void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
+ {
+ Visit(node.NamePrefix);
+ AddSemanticRange(node.Name, _semanticTokensLegend.TokenTypes.MarkupAttribute);
+ }
+
+ #endregion HTML
+
+ #region C#
+
+ public override void VisitCSharpStatementBody(CSharpStatementBodySyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.OpenBrace, tokenTypes.RazorTransition);
+
+ Visit(node.CSharpCode);
+
+ AddSemanticRange(node.CloseBrace, tokenTypes.RazorTransition);
+
+ }
+
+ public override void VisitCSharpImplicitExpressionBody(CSharpImplicitExpressionBodySyntax node)
+ {
+ // Generally same as explicit expression, below, but different because the parens might not be there,
+ // and because the compiler isn't nice and doesn't give us OpenParen and CloseParen properties we can
+ // easily use.
+
+ // Matches @(SomeCSharpCode())
+ if (node.CSharpCode.Children is
+ [
+ CSharpExpressionLiteralSyntax { LiteralTokens: [{ Kind: SyntaxKind.LeftParenthesis } openParen] },
+ CSharpExpressionLiteralSyntax body,
+ CSharpExpressionLiteralSyntax { LiteralTokens: [{ Kind: SyntaxKind.RightParenthesis } closeParen] },
+ ])
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(openParen, tokenTypes.RazorTransition);
+
+ Visit(body);
+
+ AddSemanticRange(closeParen, tokenTypes.RazorTransition);
+ }
+ else
+ {
+ // Matches @SomeCSharpCode()
+ Visit(node.CSharpCode);
+ }
+ }
+
+ public override void VisitCSharpExplicitExpressionBody(CSharpExplicitExpressionBodySyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.OpenParen, tokenTypes.RazorTransition);
+
+ Visit(node.CSharpCode);
+
+ AddSemanticRange(node.CloseParen, tokenTypes.RazorTransition);
+
+ }
+
+ #endregion C#
+
+ #region Razor
+
+ public override void VisitRazorCommentBlock(RazorCommentBlockSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.StartCommentTransition, tokenTypes.RazorCommentTransition);
+ AddSemanticRange(node.StartCommentStar, tokenTypes.RazorCommentStar);
+ AddSemanticRange(node.Comment, tokenTypes.RazorComment);
+ AddSemanticRange(node.EndCommentStar, tokenTypes.RazorCommentStar);
+ AddSemanticRange(node.EndCommentTransition, tokenTypes.RazorCommentTransition);
+ }
+
+ public override void VisitRazorMetaCode(RazorMetaCodeSyntax node)
+ {
+ if (node.Kind == SyntaxKind.RazorMetaCode)
+ {
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.RazorTransition);
+ }
+ else
+ {
+ throw new NotSupportedException("Unknown RazorMetaCode");
+ }
+ }
+
+ public override void VisitRazorDirectiveBody(RazorDirectiveBodySyntax node)
+ {
+ // We can't provide colors for CSharp because if we both provided them then they would overlap, which violates the LSP spec.
+ if (node.Keyword.Kind != SyntaxKind.CSharpStatementLiteral)
+ {
+ AddSemanticRange(node.Keyword, _semanticTokensLegend.TokenTypes.RazorDirective);
+ }
+ else
+ {
+ Visit(node.Keyword);
+ }
+
+ Visit(node.CSharpCode);
+ }
+
+ public override void VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.OpenAngle, tokenTypes.MarkupTagDelimiter);
+
+ if (node.Bang.IsValid(out var bang))
+ {
+ AddSemanticRange(bang, tokenTypes.RazorTransition);
+ }
+
+ if (ClassifyTagName((MarkupTagHelperElementSyntax)node.Parent))
+ {
+ var semanticKind = GetElementSemanticKind(node);
+ AddSemanticRange(node.Name, semanticKind);
+ }
+ else
+ {
+ AddSemanticRange(node.Name, tokenTypes.MarkupElement);
+ }
+
+ Visit(node.Attributes);
+
+ if (node.ForwardSlash.IsValid(out var forwardSlash))
+ {
+ AddSemanticRange(forwardSlash, tokenTypes.MarkupTagDelimiter);
+ }
+
+ AddSemanticRange(node.CloseAngle, tokenTypes.MarkupTagDelimiter);
+ }
+
+ public override void VisitMarkupTagHelperEndTag(MarkupTagHelperEndTagSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ AddSemanticRange(node.OpenAngle, tokenTypes.MarkupTagDelimiter);
+ AddSemanticRange(node.ForwardSlash, tokenTypes.MarkupTagDelimiter);
+
+ if (node.Bang.IsValid(out var bang))
+ {
+ AddSemanticRange(bang, tokenTypes.RazorTransition);
+ }
+
+ if (ClassifyTagName((MarkupTagHelperElementSyntax)node.Parent))
+ {
+ var semanticKind = GetElementSemanticKind(node);
+ AddSemanticRange(node.Name, semanticKind);
+ }
+ else
+ {
+ AddSemanticRange(node.Name, tokenTypes.MarkupElement);
+ }
+
+ AddSemanticRange(node.CloseAngle, tokenTypes.MarkupTagDelimiter);
+ }
+
+ public override void VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node)
+ {
+ Visit(node.NamePrefix);
+
+ if (node.TagHelperAttributeInfo.Bound)
+ {
+ var semanticKind = GetAttributeSemanticKind(node);
+ AddSemanticRange(node.Name, semanticKind);
+ }
+ else
+ {
+ AddSemanticRange(node.Name, _semanticTokensLegend.TokenTypes.MarkupAttribute);
+ }
+ }
+
+ public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ Visit(node.NamePrefix);
+ if (node.TagHelperAttributeInfo.Bound)
+ {
+ var semanticKind = GetAttributeSemanticKind(node);
+ AddSemanticRange(node.Name, semanticKind);
+ }
+ else
+ {
+ AddSemanticRange(node.Name, tokenTypes.MarkupAttribute);
+ }
+
+ Visit(node.NameSuffix);
+
+ AddSemanticRange(node.EqualsToken, tokenTypes.MarkupOperator);
+
+ AddSemanticRange(node.ValuePrefix, tokenTypes.MarkupAttributeQuote);
+ Visit(node.Value);
+ AddSemanticRange(node.ValueSuffix, tokenTypes.MarkupAttributeQuote);
+ }
+
+ public override void VisitMarkupTagHelperAttributeValue(MarkupTagHelperAttributeValueSyntax node)
+ {
+ foreach (var child in node.Children)
+ {
+ if (child.Kind == SyntaxKind.MarkupTextLiteral)
+ {
+ AddSemanticRange(child, _semanticTokensLegend.TokenTypes.MarkupAttributeValue);
+ }
+ else
+ {
+ Visit(child);
+ }
+ }
+ }
+
+ public override void VisitMarkupTagHelperDirectiveAttribute(MarkupTagHelperDirectiveAttributeSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ if (node.TagHelperAttributeInfo.Bound)
+ {
+ Visit(node.Transition);
+ Visit(node.NamePrefix);
+ AddSemanticRange(node.Name, tokenTypes.RazorDirectiveAttribute);
+ Visit(node.NameSuffix);
+
+ if (node.Colon != null)
+ {
+ AddSemanticRange(node.Colon, tokenTypes.RazorDirectiveColon);
+ }
+
+ if (node.ParameterName != null)
+ {
+ AddSemanticRange(node.ParameterName, tokenTypes.RazorDirectiveAttribute);
+ }
+ }
+
+ AddSemanticRange(node.EqualsToken, tokenTypes.MarkupOperator);
+ AddSemanticRange(node.ValuePrefix, tokenTypes.MarkupAttributeQuote);
+ Visit(node.Value);
+ AddSemanticRange(node.ValueSuffix, tokenTypes.MarkupAttributeQuote);
+ }
+
+ public override void VisitMarkupMinimizedTagHelperDirectiveAttribute(MarkupMinimizedTagHelperDirectiveAttributeSyntax node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ if (node.TagHelperAttributeInfo.Bound)
+ {
+ AddSemanticRange(node.Transition, tokenTypes.RazorTransition);
+ Visit(node.NamePrefix);
+ AddSemanticRange(node.Name, tokenTypes.RazorDirectiveAttribute);
+
+ if (node.Colon != null)
+ {
+ AddSemanticRange(node.Colon, tokenTypes.RazorDirectiveColon);
+ }
+
+ if (node.ParameterName != null)
+ {
+ AddSemanticRange(node.ParameterName, tokenTypes.RazorDirectiveAttribute);
+ }
+ }
+ }
+
+ public override void VisitCSharpTransition(CSharpTransitionSyntax node)
+ {
+ if (node.Parent is not RazorDirectiveSyntax)
+ {
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.RazorTransition);
+ }
+ else
+ {
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.RazorTransition);
+ }
+ }
+
+ public override void VisitMarkupTransition(MarkupTransitionSyntax node)
+ {
+ AddSemanticRange(node, _semanticTokensLegend.TokenTypes.RazorTransition);
+ }
+
+ #endregion Razor
+
+ private int GetElementSemanticKind(SyntaxNode node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ var semanticKind = IsComponent(node) ? tokenTypes.RazorComponentElement : tokenTypes.RazorTagHelperElement;
+ return semanticKind;
+ }
+
+ private int GetAttributeSemanticKind(SyntaxNode node)
+ {
+ var tokenTypes = _semanticTokensLegend.TokenTypes;
+
+ var semanticKind = IsComponent(node) ? tokenTypes.RazorComponentAttribute : tokenTypes.RazorTagHelperAttribute;
+ return semanticKind;
+ }
+
+ private static bool IsComponent(SyntaxNode node)
+ {
+ if (node is MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var binding })
+ {
+ var componentDescriptor = binding.Descriptors.FirstOrDefault(static d => d.Kind == TagHelperKind.Component);
+ return componentDescriptor is not null;
+ }
+ else if (node is MarkupTagHelperStartTagSyntax startTag)
+ {
+ return IsComponent(startTag.Parent);
+ }
+ else if (node is MarkupTagHelperEndTagSyntax endTag)
+ {
+ return IsComponent(endTag.Parent);
+ }
+ else if (node is MarkupTagHelperAttributeSyntax attribute)
+ {
+ return IsComponent(attribute.Parent.Parent);
+ }
+ else if (node is MarkupMinimizedTagHelperAttributeSyntax minimizedTagHelperAttribute)
+ {
+ return IsComponent(minimizedTagHelperAttribute.Parent.Parent);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ // We don't want to classify TagNames of well-known HTML
+ // elements as TagHelpers (even if they are). So the 'input' in``
+ // needs to not be marked as a TagHelper, but `` should be.
+ private static bool ClassifyTagName(MarkupTagHelperElementSyntax node)
+ {
+ if (node is null)
+ {
+ throw new ArgumentNullException(nameof(node));
+ }
+
+ if (node.StartTag?.Name != null &&
+ node.TagHelperInfo is { BindingResult: var binding })
+ {
+ return !binding.IsAttributeMatch;
+ }
+
+ return false;
+ }
+
+ private void AddSemanticRange(SyntaxNode node, int semanticKind)
+ {
+ if (node is null)
+ {
+ // This can happen in situations like " _codeFixProviders = [];
private static HashSet _codeRefactoringProviders = [];
@@ -58,8 +65,12 @@ public static class RoslynAnalysis
var host = MefHostServices.Create(container);
_workspace = MSBuildWorkspace.Create(host);
_workspace.RegisterWorkspaceFailedHandler(o => throw new InvalidOperationException($"Workspace failed: {o.Diagnostic.Message}"));
+
var snapshotManager = container.GetExports().FirstOrDefault();
_snapshotManager = snapshotManager;
+
+ _semanticTokensLegendService = container.GetExports().FirstOrDefault();
+ _semanticTokensLegendService!.SetLegend(TokenTypeProvider.ConstructTokenTypes(false), TokenTypeProvider.ConstructTokenModifiers());
}
var solution = await _workspace.OpenSolutionAsync(_sharpIdeSolutionModel.FilePath, new Progress());
timer.Stop();
@@ -188,7 +199,49 @@ public static class RoslynAnalysis
var razorText = await razorDocument.GetTextAsync(cancellationToken);
+ List relevantTypes = ["razorDirective", "razorTransition", "markupTextLiteral", "markupTagDelimiter", "markupElement", "razorComponentElement", "razorComponentAttribute", "razorComment", "razorCommentTransition", "razorCommentStar", "markupOperator", "markupAttributeQuote"];
+ var ranges = new List();
+ CustomSemanticTokensVisitor.AddSemanticRanges(ranges, razorCodeDocument, generatedDocSyntaxRoot!.FullSpan, _semanticTokensLegendService!, false);
+ var relevantRanges = ranges.Select(s =>
+ {
+ var kind = _semanticTokensLegendService!.TokenTypes.All[s.Kind];
+ return new TranslatedSemanticRange { Range = s, Kind = kind };
+ }).Where(s => relevantTypes.Contains(s.Kind)).ToList();
+
+ //var allTypes = ranges.Select(s => _semanticTokensLegendService!.TokenTypes.All[s.Kind]).Distinct().ToList();
+ var semanticRangeRazorSpans = relevantRanges.Select(s =>
+ {
+ var linePositionSpan = s.Range.AsLinePositionSpan();
+ var textSpan = razorText.GetTextSpan(linePositionSpan);
+ var sourceSpan = new SourceSpan(
+ fileModel.Path,
+ textSpan.Start,
+ linePositionSpan.Start.Line,
+ linePositionSpan.Start.Character,
+ textSpan.Length,
+ 1,
+ linePositionSpan.End.Character
+ );
+
+ return new SharpIdeRazorClassifiedSpan(sourceSpan.ToSharpIdeSourceSpan(), SharpIdeRazorSpanKind.Markup, null, s.Kind);
+ }).ToList();
+
+ // var debugMappedBackTranslatedSemanticRanges = relevantRanges.Select(s =>
+ // {
+ // var textSpan = razorText.GetTextSpan(s.Range.AsLinePositionSpan());
+ // var text = razorText.GetSubTextString(textSpan);
+ // return new { text, s };
+ // }).ToList();
+ // var semanticRangesAsRazorClassifiedSpans = ranges
+ // .Select(s =>
+ // {
+ // var sourceSpan = new SharpIdeRazorSourceSpan(null, s.)
+ // var span = new SharpIdeRazorClassifiedSpan();
+ // return span;
+ // }).ToList();
+ //var test = _semanticTokensLegendService.TokenTypes.All;
var (razorSpans, sourceMappings) = RazorAccessors.GetSpansAndMappingsForRazorCodeDocument(razorCodeDocument, razorCSharpDocument);
+ List sharpIdeRazorSpans = [];
var classifiedSpans = await Classifier.GetClassifiedSpansAsync(generatedDocument, generatedDocSyntaxRoot!.FullSpan, cancellationToken);
var roslynMappedSpans = classifiedSpans.Select(s =>
@@ -220,14 +273,15 @@ public static class RoslynAnalysis
return null;
}).Where(s => s is not null).ToList();
- razorSpans = [
- ..razorSpans.Where(s => s.Kind is not SharpIdeRazorSpanKind.Code),
- ..roslynMappedSpans.Select(s => new SharpIdeRazorClassifiedSpan(s!.SourceSpanInRazor, SharpIdeRazorSpanKind.Code, s.CsharpClassificationType))
+ sharpIdeRazorSpans = [
+ ..sharpIdeRazorSpans.Where(s => s.Kind is not SharpIdeRazorSpanKind.Code),
+ ..roslynMappedSpans.Select(s => new SharpIdeRazorClassifiedSpan(s!.SourceSpanInRazor, SharpIdeRazorSpanKind.Code, s.CsharpClassificationType)),
+ ..semanticRangeRazorSpans
];
- razorSpans = razorSpans.OrderBy(s => s.Span.AbsoluteIndex).ToImmutableArray();
+ sharpIdeRazorSpans = sharpIdeRazorSpans.OrderBy(s => s.Span.AbsoluteIndex).ToList();
timer.Stop();
Console.WriteLine($"RoslynAnalysis: Razor syntax highlighting for {fileModel.Name} took {timer.ElapsedMilliseconds}ms");
- return razorSpans;
+ return sharpIdeRazorSpans;
}
public static async Task> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel)
diff --git a/src/SharpIDE.Application/Features/Analysis/TokenTypeProvider.cs b/src/SharpIDE.Application/Features/Analysis/TokenTypeProvider.cs
new file mode 100644
index 0000000..45a1c9f
--- /dev/null
+++ b/src/SharpIDE.Application/Features/Analysis/TokenTypeProvider.cs
@@ -0,0 +1,29 @@
+using System.Collections.Immutable;
+using System.Reflection;
+using Microsoft.CodeAnalysis.ExternalAccess.Razor;
+using Microsoft.CodeAnalysis.Razor.SemanticTokens;
+
+namespace SharpIDE.Application.Features.Analysis;
+
+public static class TokenTypeProvider
+{
+ public static string[] ConstructTokenTypes(bool supportsVsExtensions)
+ {
+ string[] types = [.. RazorSemanticTokensAccessor.GetTokenTypes(supportsVsExtensions), .. GetStaticFieldValues(typeof(SemanticTokenTypes))];
+ //return new SemanticTokenTypes(types);
+ return types;
+ }
+
+ public static string[] ConstructTokenModifiers()
+ {
+ string[] types = [ .. RazorSemanticTokensAccessor.GetTokenModifiers(), .. GetStaticFieldValues(typeof(SemanticTokenModifiers))];
+ //return new SemanticTokenModifiers(types);
+ return types;
+ }
+
+ private static ImmutableArray GetStaticFieldValues(Type type)
+ {
+ var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static).Select(s => s.GetValue(null)).OfType().ToImmutableArray();
+ return fields;
+ }
+}
diff --git a/src/SharpIDE.Application/Features/Analysis/TranslatedSemanticRange.cs b/src/SharpIDE.Application/Features/Analysis/TranslatedSemanticRange.cs
new file mode 100644
index 0000000..29a2869
--- /dev/null
+++ b/src/SharpIDE.Application/Features/Analysis/TranslatedSemanticRange.cs
@@ -0,0 +1,9 @@
+using Microsoft.CodeAnalysis.Razor.SemanticTokens;
+
+namespace SharpIDE.Application.Features.Analysis;
+
+public class TranslatedSemanticRange
+{
+ public required SemanticRange Range { get; set; }
+ public required string Kind { get; set; }
+}
diff --git a/src/SharpIDE.Application/SharpIDE.Application.csproj b/src/SharpIDE.Application/SharpIDE.Application.csproj
index 747bf65..b77a2e7 100644
--- a/src/SharpIDE.Application/SharpIDE.Application.csproj
+++ b/src/SharpIDE.Application/SharpIDE.Application.csproj
@@ -8,10 +8,13 @@
-
-
+
+
+
+
+
@@ -32,6 +35,7 @@
+
diff --git a/src/SharpIDE.Godot/CustomSyntaxHighlighter.cs b/src/SharpIDE.Godot/CustomSyntaxHighlighter.cs
index 2d03401..c50f4b3 100644
--- a/src/SharpIDE.Godot/CustomSyntaxHighlighter.cs
+++ b/src/SharpIDE.Godot/CustomSyntaxHighlighter.cs
@@ -57,7 +57,7 @@ public partial class CustomHighlighter : SyntaxHighlighter
var highlightInfo = new Dictionary
{
- { ColorStringName, GetColorForRazorSpanKind(razorSpan.Kind, razorSpan.CodeClassificationType) }
+ { ColorStringName, GetColorForRazorSpanKind(razorSpan.Kind, razorSpan.CodeClassificationType, razorSpan.VsSemanticRangeType) }
};
highlights[columnIndex] = highlightInfo;
@@ -66,19 +66,36 @@ public partial class CustomHighlighter : SyntaxHighlighter
return highlights;
}
- private static Color GetColorForRazorSpanKind(SharpIdeRazorSpanKind kind, string? codeClassificationType)
+ private static Color GetColorForRazorSpanKind(SharpIdeRazorSpanKind kind, string? codeClassificationType, string? vsSemanticRangeType)
{
return kind switch
{
SharpIdeRazorSpanKind.Code => GetColorForClassification(codeClassificationType!),
SharpIdeRazorSpanKind.Comment => new Color("57a64a"), // green
SharpIdeRazorSpanKind.MetaCode => new Color("a699e6"), // purple
- SharpIdeRazorSpanKind.Markup => new Color("0b7f7f"), // dark green
+ SharpIdeRazorSpanKind.Markup => GetColorForMarkupSpanKind(vsSemanticRangeType),
SharpIdeRazorSpanKind.Transition => new Color("a699e6"), // purple
SharpIdeRazorSpanKind.None => new Color("dcdcdc"),
_ => new Color("dcdcdc")
};
}
+
+ private static Color GetColorForMarkupSpanKind(string? vsSemanticRangeType)
+ {
+ return vsSemanticRangeType switch
+ {
+ "razorDirective" or "razorTransition" => new Color("a699e6"), // purple
+ "markupTagDelimiter" => new Color("808080"), // gray
+ "markupTextLiteral" => new Color("dcdcdc"), // white
+ "markupElement" => new Color("569cd6"), // blue
+ "razorComponentElement" => new Color("0b7f7f"), // dark green
+ "razorComponentAttribute" => new Color("dcdcdc"), // white
+ "razorComment" or "razorCommentStar" or "razorCommentTransition" => new Color("57a64a"), // green
+ "markupOperator" =>new Color("dcdcdc"), // white
+ "markupAttributeQuote" => new Color("dcdcdc"), // white
+ _ => new Color("dcdcdc") // default to white
+ };
+ }
private Dictionary MapClassifiedSpansToHighlights(int line)
diff --git a/src/SharpIDE.Godot/SharpIDE.Godot.csproj b/src/SharpIDE.Godot/SharpIDE.Godot.csproj
index 111bb46..149de62 100644
--- a/src/SharpIDE.Godot/SharpIDE.Godot.csproj
+++ b/src/SharpIDE.Godot/SharpIDE.Godot.csproj
@@ -4,14 +4,13 @@
true
enable
true
- false
-
-
-
+
+
+
\ No newline at end of file
diff --git a/src/SharpIDE.Godot/SharpIDE.Godot.sln b/src/SharpIDE.Godot/SharpIDE.Godot.sln
index 4de37fe..4d5f9bd 100644
--- a/src/SharpIDE.Godot/SharpIDE.Godot.sln
+++ b/src/SharpIDE.Godot/SharpIDE.Godot.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\..\nuget.config = ..\..\nuget.config
..\..\README.md = ..\..\README.md
..\..\Directory.Packages.props = ..\..\Directory.Packages.props
+ ..\..\.globalconfig = ..\..\.globalconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpIDE.RazorAccess", "..\SharpIDE.RazorAccess\SharpIDE.RazorAccess.csproj", "{614547C3-6620-4F37-B0A9-AA78A4293EB4}"
diff --git a/src/SharpIDE.RazorAccess/SharpIdeRazorClassifiedSpan.cs b/src/SharpIDE.RazorAccess/SharpIdeRazorClassifiedSpan.cs
index d864c2c..08f528f 100644
--- a/src/SharpIDE.RazorAccess/SharpIdeRazorClassifiedSpan.cs
+++ b/src/SharpIDE.RazorAccess/SharpIdeRazorClassifiedSpan.cs
@@ -3,7 +3,7 @@ using RazorCodeDocumentExtensions = WorkspaceAlias::Microsoft.AspNetCore.Razor.L
namespace SharpIDE.RazorAccess;
-public record struct SharpIdeRazorClassifiedSpan(SharpIdeRazorSourceSpan Span, SharpIdeRazorSpanKind Kind, string? CodeClassificationType = null);
+public record struct SharpIdeRazorClassifiedSpan(SharpIdeRazorSourceSpan Span, SharpIdeRazorSpanKind Kind, string? CodeClassificationType = null, string? VsSemanticRangeType = null);
public enum SharpIdeRazorSpanKind
{