add razor file syntax highlighting

This commit is contained in:
Matt Parker
2025-09-14 19:02:33 +10:00
parent b4291cb7f7
commit cc7e766966
19 changed files with 590 additions and 49 deletions

View File

@@ -0,0 +1,72 @@
extern alias WorkspaceAlias;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
using RazorCodeDocumentExtensions = WorkspaceAlias::Microsoft.AspNetCore.Razor.Language.RazorCodeDocumentExtensions;
namespace SharpIDE.RazorAccess;
public static class RazorAccessors
{
public static (ImmutableArray<SharpIdeRazorClassifiedSpan>, SourceText Text, List<SharpIdeRazorSourceMapping>) GetClassifiedSpans(SourceText sourceText, SourceText importsSourceText, string razorDocumentFilePath, string projectDirectory)
{
var razorSourceDocument = RazorSourceDocument.Create(sourceText.ToString(), razorDocumentFilePath);
var importsRazorSourceDocument = RazorSourceDocument.Create(importsSourceText.ToString(), "_Imports.razor");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, RazorProjectFileSystem.Create(projectDirectory),
builder => { /* configure features if needed */ });
//var razorCodeDocument = projectEngine.Process(razorSourceDocument, RazorFileKind.Component, [], []);
var razorCodeDocument = projectEngine.ProcessDesignTime(razorSourceDocument, RazorFileKind.Component, [importsRazorSourceDocument], []);
var razorCSharpDocument = razorCodeDocument.GetRequiredCSharpDocument();
//var generatedSourceText = razorCSharpDocument.Text;
//var filePath = razorCodeDocument.Source.FilePath.AssumeNotNull();
//var razorSourceText = razorCodeDocument.Source.Text;
var razorSpans = RazorCodeDocumentExtensions.GetClassifiedSpans(razorCodeDocument);
//var sharpIdeSpans = MemoryMarshal.Cast<RazorCodeDocumentExtensions.ClassifiedSpan, SharpIdeRazorClassifiedSpan>(razorSpans);
var sharpIdeSpans = razorSpans.Select(s => new SharpIdeRazorClassifiedSpan(s.Span.ToSharpIdeSourceSpan(), s.Kind.ToSharpIdeSpanKind())).ToList();
return (sharpIdeSpans.ToImmutableArray(), razorCSharpDocument.Text, razorCSharpDocument.SourceMappings.Select(s => s.ToSharpIdeSourceMapping()).ToList());
}
// public static bool TryGetMappedSpans(
// TextSpan span,
// SourceText source,
// RazorCSharpDocument output,
// out LinePositionSpan linePositionSpan,
// out TextSpan mappedSpan)
// {
// foreach (SourceMapping sourceMapping in output.SourceMappings)
// {
// TextSpan textSpan1 = sourceMapping.OriginalSpan.AsTextSpan();
// TextSpan textSpan2 = sourceMapping.GeneratedSpan.AsTextSpan();
// if (textSpan2.Contains(span))
// {
// int num1 = span.Start - textSpan2.Start;
// int num2 = span.End - textSpan2.End;
// if (num1 >= 0 && num2 <= 0)
// {
// mappedSpan = new TextSpan(textSpan1.Start + num1, textSpan1.End + num2 - (textSpan1.Start + num1));
// linePositionSpan = source.Lines.GetLinePositionSpan(mappedSpan);
// return true;
// }
// }
// }
// mappedSpan = new TextSpan();
// linePositionSpan = new LinePositionSpan();
// return false;
// }
// /// <summary>
// /// Wrapper to avoid <see cref="MissingMethodException"/>s in the caller during JITing
// /// even though the method is not actually called.
// /// </summary>
// [MethodImpl(MethodImplOptions.NoInlining)]
// private static object GetFileKindFromPath(string filePath)
// {
// return FileKinds.GetFileKindFromPath(filePath);
// }
}

View File

@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>Microsoft.CodeAnalysis.Razor.Test</AssemblyName>
<KeyOriginatorFile>$(PkgMicrosoft_DotNet_Arcade_Sdk)\tools\snk\AspNetCore.snk</KeyOriginatorFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" />
<PackageReference Include="Microsoft.DotNet.Arcade.Sdk" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Net.Compilers.Razor.Toolset" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Utilities.Shared" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.CodeAnalysis.Razor.Compiler" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.CodeAnalysis.Razor.Workspaces" PrivateAssets="all" IncludeAssets="none" GeneratePathProperty="true" />
<PackageReference Include="Krafs.Publicizer">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<!-- <Reference Include="$(PkgMicrosoft_Net_Compilers_Razor_Toolset)\source-generators\Microsoft.CodeAnalysis.Razor.Compiler.dll" />-->
<Reference Aliases="WorkspaceAlias" Include="$(PkgMicrosoft_CodeAnalysis_Razor_Workspaces)\lib\net9.0\Microsoft.CodeAnalysis.Razor.Workspaces.dll" />
<Reference Include="$(PkgMicrosoft_CodeAnalysis_Razor_Compiler)\lib\net9.0\Microsoft.CodeAnalysis.Razor.Compiler.dll" />
<Reference Include="$(PkgMicrosoft_AspNetCore_Razor_Utilities_Shared)\lib\net9.0\Microsoft.AspNetCore.Razor.Utilities.Shared.dll" />
<Reference Include="$(PkgMicrosoft_Extensions_ObjectPool)\lib\net8.0\Microsoft.Extensions.ObjectPool.dll" />
</ItemGroup>
<ItemGroup>
<Publicize Include="Microsoft.CodeAnalysis.Razor.Workspaces" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,30 @@
extern alias WorkspaceAlias;
using RazorCodeDocumentExtensions = WorkspaceAlias::Microsoft.AspNetCore.Razor.Language.RazorCodeDocumentExtensions;
namespace SharpIDE.RazorAccess;
public record struct SharpIdeRazorClassifiedSpan(SharpIdeRazorSourceSpan Span, SharpIdeRazorSpanKind Kind, string? CodeClassificationType = null);
public enum SharpIdeRazorSpanKind
{
Transition,
MetaCode,
Comment,
Code,
Markup,
None,
}
public static class SharpIdeRazorClassifiedSpanExtensions
{
public static SharpIdeRazorSpanKind ToSharpIdeSpanKind(this RazorCodeDocumentExtensions.SpanKind kind) => kind switch
{
RazorCodeDocumentExtensions.SpanKind.Transition => SharpIdeRazorSpanKind.Transition,
RazorCodeDocumentExtensions.SpanKind.MetaCode => SharpIdeRazorSpanKind.MetaCode,
RazorCodeDocumentExtensions.SpanKind.Comment => SharpIdeRazorSpanKind.Comment,
RazorCodeDocumentExtensions.SpanKind.Code => SharpIdeRazorSpanKind.Code,
RazorCodeDocumentExtensions.SpanKind.Markup => SharpIdeRazorSpanKind.Markup,
RazorCodeDocumentExtensions.SpanKind.None => SharpIdeRazorSpanKind.None,
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null)
};
}

View File

@@ -0,0 +1,49 @@
using System.Globalization;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.Internal;
namespace SharpIDE.RazorAccess;
public sealed class SharpIdeRazorSourceMapping(
SharpIdeRazorSourceSpan originalSpan,
SharpIdeRazorSourceSpan generatedSpan)
: IEquatable<SharpIdeRazorSourceMapping>
{
public SharpIdeRazorSourceSpan OriginalSpan { get; } = originalSpan;
public SharpIdeRazorSourceSpan GeneratedSpan { get; } = generatedSpan;
public override bool Equals(object? obj) => Equals(obj as SourceMapping);
public bool Equals(SharpIdeRazorSourceMapping? other)
{
if (other == null)
return false;
var sourceSpan = OriginalSpan;
if (!sourceSpan.Equals(other.OriginalSpan))
return false;
sourceSpan = GeneratedSpan;
return sourceSpan.Equals(other.GeneratedSpan);
}
public override int GetHashCode()
{
HashCodeCombiner hashCode = HashCodeCombiner.Start();
hashCode.Add(OriginalSpan);
hashCode.Add(GeneratedSpan);
return hashCode;
}
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "{0} -> {1}", OriginalSpan, GeneratedSpan);
}
}
public static class SharpIdeRazorSourceMappingExtensions
{
public static SharpIdeRazorSourceMapping ToSharpIdeSourceMapping(this SourceMapping mapping)
{
return new SharpIdeRazorSourceMapping(mapping.OriginalSpan.ToSharpIdeSourceSpan(), mapping.GeneratedSpan.ToSharpIdeSourceSpan());
}
}

View File

@@ -0,0 +1,83 @@
using System.Globalization;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Internal;
namespace SharpIDE.RazorAccess;
public readonly struct SharpIdeRazorSourceSpan(
string filePath,
int absoluteIndex,
int lineIndex,
int characterIndex,
int length,
int lineCount,
int endCharacterIndex)
: IEquatable<SharpIdeRazorSourceSpan>
{
public int Length { get; } = length;
public int AbsoluteIndex { get; } = absoluteIndex;
public int LineIndex { get; } = lineIndex;
public int CharacterIndex { get; } = characterIndex;
public int LineCount { get; } = lineCount;
public int EndCharacterIndex { get; } = endCharacterIndex;
public string FilePath { get; } = filePath;
public bool Equals(SharpIdeRazorSourceSpan other)
{
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) && this.AbsoluteIndex == other.AbsoluteIndex && this.LineIndex == other.LineIndex && this.CharacterIndex == other.CharacterIndex && this.Length == other.Length;
}
public override bool Equals(object? obj) => obj is SharpIdeRazorSourceSpan other && Equals(other);
public override int GetHashCode()
{
var hashCode = HashCodeCombiner.Start();
hashCode.Add(FilePath, StringComparer.Ordinal);
hashCode.Add(AbsoluteIndex);
hashCode.Add(LineIndex);
hashCode.Add(CharacterIndex);
hashCode.Add(Length);
return hashCode;
}
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"({0}:{1},{2} [{3}] {4})",
this.AbsoluteIndex,
this.LineIndex,
this.CharacterIndex,
this.Length,
this.FilePath
);
}
public static bool operator ==(SharpIdeRazorSourceSpan left, SharpIdeRazorSourceSpan right)
{
return left.Equals(right);
}
public static bool operator !=(SharpIdeRazorSourceSpan left, SharpIdeRazorSourceSpan right)
{
return !(left == right);
}
}
public static class SharpIdeRazorSourceSpanExtensions
{
public static TextSpan AsTextSpan(this SharpIdeRazorSourceSpan sourceSpan)
{
return new TextSpan(sourceSpan.AbsoluteIndex, sourceSpan.Length);
}
public static SharpIdeRazorSourceSpan ToSharpIdeSourceSpan(this SourceSpan span)
=> new SharpIdeRazorSourceSpan(
span.FilePath,
span.AbsoluteIndex,
span.LineIndex,
span.CharacterIndex,
span.Length,
span.LineCount,
span.EndCharacterIndex);
}