зеркало из https://github.com/dotnet/razor.git
Switch Razor to use only semantic token range handling (#5949)
This commit is contained in:
Родитель
27439206fb
Коммит
62a2c1d616
|
@ -3,8 +3,8 @@
|
|||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.Language;
|
|||
using Microsoft.AspNetCore.Razor.LanguageServer;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer
|
|||
|
||||
private DocumentSnapshot DocumentSnapshot { get; set; }
|
||||
|
||||
private DocumentSnapshot UpdatedDocumentSnapshot { get; set; }
|
||||
private Range Range { get; set; }
|
||||
|
||||
private ProjectSnapshotManagerDispatcher ProjectSnapshotManagerDispatcher { get; set; }
|
||||
|
||||
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer
|
|||
|
||||
private string TargetPath { get; set; }
|
||||
|
||||
[GlobalSetup(Target = nameof(RazorSemanticTokensEditAsync))]
|
||||
[GlobalSetup(Target = nameof(RazorSemanticTokensRangeAsync))]
|
||||
public async Task InitializeRazorSemanticAsync()
|
||||
{
|
||||
await EnsureServicesInitializedAsync();
|
||||
|
@ -54,26 +55,37 @@ namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer
|
|||
PagesDirectory = Path.Combine(projectRoot, "Components", "Pages");
|
||||
var filePath = Path.Combine(PagesDirectory, $"SemanticTokens.razor");
|
||||
TargetPath = "/Components/Pages/SemanticTokens.razor";
|
||||
var updatedPath = Path.Combine(PagesDirectory, $"Append.extra");
|
||||
|
||||
DocumentUri = DocumentUri.File(filePath);
|
||||
DocumentSnapshot = GetDocumentSnapshot(ProjectFilePath, filePath, TargetPath);
|
||||
UpdatedDocumentSnapshot = GetDocumentSnapshot(ProjectFilePath, updatedPath, TargetPath);
|
||||
|
||||
var text = await DocumentSnapshot.GetTextAsync().ConfigureAwait(false);
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = text.Lines.Count - 1,
|
||||
Character = text.Lines.Last().Span.Length - 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Razor Semantic Tokens Formatting")]
|
||||
public async Task RazorSemanticTokensEditAsync()
|
||||
[Benchmark(Description = "Razor Semantic Tokens Range Handling")]
|
||||
public async Task RazorSemanticTokensRangeAsync()
|
||||
{
|
||||
var textDocumentIdentifier = new TextDocumentIdentifier(DocumentUri);
|
||||
var cancellationToken = CancellationToken.None;
|
||||
var firstVersion = 1;
|
||||
var documentVersion = 1;
|
||||
var semanticVersion = new VersionStamp();
|
||||
|
||||
await UpdateDocumentAsync(firstVersion, DocumentSnapshot).ConfigureAwait(false);
|
||||
var fullResult = await RazorSemanticTokenService.GetSemanticTokensAsync(textDocumentIdentifier, DocumentSnapshot, firstVersion, range: null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var secondVersion = 2;
|
||||
await UpdateDocumentAsync(secondVersion, UpdatedDocumentSnapshot).ConfigureAwait(false);
|
||||
_ = await RazorSemanticTokenService.GetSemanticTokensEditsAsync(UpdatedDocumentSnapshot, secondVersion, textDocumentIdentifier, fullResult.ResultId, cancellationToken).ConfigureAwait(false);
|
||||
await UpdateDocumentAsync(documentVersion, DocumentSnapshot).ConfigureAwait(false);
|
||||
await RazorSemanticTokenService.GetSemanticTokensAsync(
|
||||
textDocumentIdentifier, DocumentSnapshot, documentVersion, semanticVersion, Range, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task UpdateDocumentAsync(int newVersion, DocumentSnapshot documentSnapshot)
|
||||
|
@ -121,7 +133,7 @@ namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer
|
|||
}
|
||||
|
||||
// We can't get C# responses without significant amounts of extra work, so let's just shim it for now, any non-Null result is fine.
|
||||
internal override Task<VersionedSemanticRange> GetCSharpSemanticRangesAsync(
|
||||
internal override Task<SemanticRangeResponse> GetCSharpSemanticRangesAsync(
|
||||
RazorCodeDocument codeDocument,
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
Range range,
|
||||
|
@ -129,7 +141,8 @@ namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer
|
|||
CancellationToken cancellationToken,
|
||||
string previousResultId = null)
|
||||
{
|
||||
return Task.FromResult(new VersionedSemanticRange(new List<SemanticRange>(), IsFinalizedCSharp: false));
|
||||
var result = SemanticRangeResponse.Default;
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Extensions
|
|||
{
|
||||
internal static class RangeExtensions
|
||||
{
|
||||
public static readonly Range UndefinedRange = new Range
|
||||
public static readonly Range UndefinedRange = new()
|
||||
{
|
||||
Start = new Position(-1, -1),
|
||||
End = new Position(-1, -1)
|
||||
|
@ -131,18 +131,62 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Extensions
|
|||
throw new ArgumentNullException(nameof(sourceText));
|
||||
}
|
||||
|
||||
if (range.Start.Line >= sourceText.Lines.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Range start line {range.Start.Line} matches or exceeds SourceText boundary {sourceText.Lines.Count}.");
|
||||
}
|
||||
|
||||
if (range.End.Line >= sourceText.Lines.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Range end line {range.End.Line} matches or exceeds SourceText boundary {sourceText.Lines.Count}.");
|
||||
}
|
||||
|
||||
var start = sourceText.Lines[range.Start.Line].Start + range.Start.Character;
|
||||
var end = sourceText.Lines[range.End.Line].Start + range.End.Character;
|
||||
|
||||
var length = end - start;
|
||||
if (length < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"{range} resolved to a negative length.");
|
||||
throw new ArgumentOutOfRangeException($"{range} resolved to zero or negative length.");
|
||||
}
|
||||
|
||||
return new TextSpan(start, length);
|
||||
}
|
||||
|
||||
public static Language.Syntax.TextSpan AsRazorTextSpan(this Range range, SourceText sourceText)
|
||||
{
|
||||
if (range is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(range));
|
||||
}
|
||||
|
||||
if (sourceText is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceText));
|
||||
}
|
||||
|
||||
if (range.Start.Line >= sourceText.Lines.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Range start line {range.Start.Line} matches or exceeds SourceText boundary {sourceText.Lines.Count}.");
|
||||
}
|
||||
|
||||
if (range.End.Line >= sourceText.Lines.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Range end line {range.End.Line} matches or exceeds SourceText boundary {sourceText.Lines.Count}.");
|
||||
}
|
||||
|
||||
var start = sourceText.Lines[range.Start.Line].Start + range.Start.Character;
|
||||
var end = sourceText.Lines[range.End.Line].Start + range.End.Character;
|
||||
|
||||
var length = end - start;
|
||||
if (length < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"{range} resolved to zero or negative length.");
|
||||
}
|
||||
|
||||
return new Language.Syntax.TextSpan(start, length);
|
||||
}
|
||||
|
||||
public static bool IsUndefined(this Range range)
|
||||
{
|
||||
if (range is null)
|
||||
|
|
|
@ -9,11 +9,10 @@ using Microsoft.Extensions.Logging;
|
|||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
||||
{
|
||||
internal class RazorSemanticTokensEndpoint : ISemanticTokensFullHandler, ISemanticTokensRangeHandler, ISemanticTokensDeltaHandler
|
||||
internal class RazorSemanticTokensEndpoint : ISemanticTokensRangeHandler
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly RazorSemanticTokensInfoService _semanticTokensInfoService;
|
||||
|
@ -36,16 +35,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
_logger = loggerFactory.CreateLogger<RazorSemanticTokensEndpoint>();
|
||||
}
|
||||
|
||||
public async Task<SemanticTokens?> Handle(SemanticTokensParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
return await HandleAsync(request.TextDocument.Uri.GetAbsolutePath(), cancellationToken, range: null);
|
||||
}
|
||||
|
||||
public async Task<SemanticTokens?> Handle(SemanticTokensRangeParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request is null)
|
||||
|
@ -53,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var semanticTokens = await HandleAsync(request.TextDocument, cancellationToken, request.Range);
|
||||
var semanticTokens = await _semanticTokensInfoService.GetSemanticTokensAsync(request.TextDocument, request.Range, cancellationToken);
|
||||
var amount = semanticTokens is null ? "no" : (semanticTokens.Data.Length / 5).ToString(Thread.CurrentThread.CurrentCulture);
|
||||
|
||||
_logger.LogInformation($"Returned {amount} semantic tokens for range {request.Range} in {request.TextDocument.Uri}.");
|
||||
|
@ -61,37 +50,15 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
return semanticTokens;
|
||||
}
|
||||
|
||||
public async Task<SemanticTokensFullOrDelta?> Handle(SemanticTokensDeltaParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var edits = await _semanticTokensInfoService.GetSemanticTokensEditsAsync(request.TextDocument, request.PreviousResultId, cancellationToken);
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
public SemanticTokensRegistrationOptions GetRegistrationOptions(SemanticTokensCapability capability, ClientCapabilities clientCapabilities)
|
||||
{
|
||||
return new SemanticTokensRegistrationOptions
|
||||
{
|
||||
DocumentSelector = RazorDefaults.Selector,
|
||||
Full = new SemanticTokensCapabilityRequestFull
|
||||
{
|
||||
Delta = true,
|
||||
},
|
||||
Full = false,
|
||||
Legend = RazorSemanticTokensLegend.Instance,
|
||||
Range = false,
|
||||
Range = true,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SemanticTokens?> HandleAsync(TextDocumentIdentifier textDocument, CancellationToken cancellationToken, Range? range = null)
|
||||
{
|
||||
var tokens = await _semanticTokensInfoService.GetSemanticTokensAsync(textDocument, range, cancellationToken);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
private readonly DocumentVersionCache _documentVersionCache;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
// Maps (docURI -> (resultId -> tokens)). We cache per-doc instead of storing all tokens
|
||||
// in one giant cache to improve colorization speeds when working with multiple files.
|
||||
private const int MaxCachesPerDoc = 6;
|
||||
private readonly MemoryCache<DocumentUri, MemoryCache<string, VersionedSemanticTokens>> _razorDocTokensCache = new();
|
||||
// Caches the last response per-document to potentially save on computation costs.
|
||||
private readonly MemoryCache<DocumentUri, SemanticTokensCacheResponse> _cachedResponses = new();
|
||||
|
||||
public DefaultRazorSemanticTokensInfoService(
|
||||
ClientNotifierServiceBase languageServer,
|
||||
|
@ -61,16 +59,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
_logger = loggerFactory.CreateLogger<DefaultRazorSemanticTokensInfoService>();
|
||||
}
|
||||
|
||||
public override Task<SemanticTokens?> GetSemanticTokensAsync(
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return GetSemanticTokensAsync(textDocumentIdentifier, range: null, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task<SemanticTokens?> GetSemanticTokensAsync(
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
Range? range,
|
||||
Range range,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var documentPath = textDocumentIdentifier.Uri.GetAbsolutePath();
|
||||
|
@ -87,17 +78,37 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
|
||||
var (documentSnapshot, documentVersion) = documentInfo.Value;
|
||||
|
||||
var tokens = await GetSemanticTokensAsync(textDocumentIdentifier, documentSnapshot, documentVersion, range, cancellationToken);
|
||||
var semanticVersion = await GetDocumentSemanticVersionAsync(documentSnapshot).ConfigureAwait(false);
|
||||
|
||||
// If we have a matching cached response, avoid computation and return early.
|
||||
if (_cachedResponses.TryGetValue(textDocumentIdentifier.Uri, out var response) &&
|
||||
response.IsCSharpFinalized &&
|
||||
response.SemanticVersion == semanticVersion &&
|
||||
response.Range == range)
|
||||
{
|
||||
return response.SemanticTokens;
|
||||
}
|
||||
|
||||
var tokens = await GetSemanticTokensAsync(
|
||||
textDocumentIdentifier, documentSnapshot, documentVersion, semanticVersion, range, cancellationToken);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static async Task<VersionStamp> GetDocumentSemanticVersionAsync(DocumentSnapshot documentSnapshot)
|
||||
{
|
||||
var documentVersionStamp = await documentSnapshot.GetTextVersionAsync();
|
||||
var semanticVersion = documentVersionStamp.GetNewerVersion(documentSnapshot.Project.Version);
|
||||
|
||||
return semanticVersion;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal async Task<SemanticTokens?> GetSemanticTokensAsync(
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
DocumentSnapshot documentSnapshot,
|
||||
int documentVersion,
|
||||
Range? range,
|
||||
VersionStamp semanticVersion,
|
||||
Range range,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var codeDocument = await GetRazorCodeDocumentAsync(documentSnapshot);
|
||||
|
@ -109,13 +120,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var razorSemanticRanges = TagHelperSemanticRangeVisitor.VisitAllNodes(codeDocument, range);
|
||||
IReadOnlyList<SemanticRange>? csharpSemanticRanges = null;
|
||||
string? newResultId = null;
|
||||
var isFinalizedCSharp = false;
|
||||
var isCSharpFinalized = false;
|
||||
|
||||
try
|
||||
{
|
||||
(csharpSemanticRanges, isFinalizedCSharp) = await GetCSharpSemanticRangesAsync(
|
||||
codeDocument, textDocumentIdentifier, range, documentVersion, cancellationToken);
|
||||
(csharpSemanticRanges, isCSharpFinalized) = await GetCSharpSemanticRangesAsync(
|
||||
codeDocument, textDocumentIdentifier, range, documentVersion, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -132,153 +142,14 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
return null;
|
||||
}
|
||||
|
||||
var semanticVersion = await GetDocumentSemanticVersionAsync(documentSnapshot);
|
||||
var data = ConvertSemanticRangesToSemanticTokensData(combinedSemanticRanges, codeDocument);
|
||||
var tokens = new SemanticTokens { Data = data };
|
||||
|
||||
if (newResultId is null)
|
||||
{
|
||||
// If there's no C# in the Razor doc, we won't have a resultId returned to us.
|
||||
// Just use a GUID instead.
|
||||
newResultId = Guid.NewGuid().ToString();
|
||||
}
|
||||
// Cache the result so we can potentially avoid recomputation next time around.
|
||||
var cacheResponse = new SemanticTokensCacheResponse(semanticVersion, range, tokens, isCSharpFinalized);
|
||||
_cachedResponses.Set(textDocumentIdentifier.Uri, cacheResponse);
|
||||
|
||||
var razorSemanticTokens = ConvertSemanticRangesToSemanticTokens(
|
||||
combinedSemanticRanges, codeDocument, newResultId, isFinalizedCSharp);
|
||||
UpdateRazorDocCache(textDocumentIdentifier.Uri, semanticVersion, newResultId, razorSemanticTokens);
|
||||
|
||||
return new SemanticTokens { ResultId = razorSemanticTokens.ResultId, Data = razorSemanticTokens.Data.ToImmutableArray() };
|
||||
}
|
||||
|
||||
public override async Task<SemanticTokensFullOrDelta?> GetSemanticTokensEditsAsync(
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
string? previousResultId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var documentPath = textDocumentIdentifier.Uri.GetAbsolutePath();
|
||||
if (documentPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentInfo = await TryGetDocumentInfoAsync(documentPath, cancellationToken).ConfigureAwait(false);
|
||||
if (documentInfo is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var (documentSnapshot, documentVersion) = documentInfo.Value;
|
||||
|
||||
return await GetSemanticTokensEditsAsync(
|
||||
documentSnapshot,
|
||||
documentVersion,
|
||||
textDocumentIdentifier,
|
||||
previousResultId,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal async Task<SemanticTokensFullOrDelta?> GetSemanticTokensEditsAsync(
|
||||
DocumentSnapshot documentSnapshot,
|
||||
long documentVersion,
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
string? previousResultId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
VersionStamp? cachedSemanticVersion = null;
|
||||
IReadOnlyList<int>? previousResults = null;
|
||||
var csharpTokensFinalized = false;
|
||||
|
||||
// Attempting to retrieve cached tokens for the Razor document.
|
||||
if (previousResultId != null &&
|
||||
_razorDocTokensCache.TryGetValue(textDocumentIdentifier.Uri, out var documentCache) &&
|
||||
documentCache.TryGetValue(previousResultId, out var cachedTokens))
|
||||
{
|
||||
previousResults = cachedTokens?.SemanticTokens;
|
||||
cachedSemanticVersion = cachedTokens?.SemanticVersion;
|
||||
|
||||
if (cachedTokens is not null)
|
||||
{
|
||||
csharpTokensFinalized = cachedTokens.IsFinalizedCSharp;
|
||||
}
|
||||
}
|
||||
|
||||
var semanticVersion = await GetDocumentSemanticVersionAsync(documentSnapshot);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// We have to recompute tokens in two scenarios:
|
||||
// 1) SemanticVersion is different. Occurs if there's been any text edits to the
|
||||
// Razor file or ProjectVersion has changed.
|
||||
// 2) C# returned non-finalized tokens to us the last time around. May occur if a
|
||||
// partial compilation was used to compute tokens.
|
||||
if (semanticVersion == default || cachedSemanticVersion != semanticVersion || !csharpTokensFinalized)
|
||||
{
|
||||
var codeDocument = await GetRazorCodeDocumentAsync(documentSnapshot);
|
||||
if (codeDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var razorSemanticRanges = TagHelperSemanticRangeVisitor.VisitAllNodes(codeDocument);
|
||||
|
||||
var (csharpSemanticRanges, isFinalizedCSharp) = await GetCSharpSemanticRangesAsync(
|
||||
codeDocument,
|
||||
textDocumentIdentifier,
|
||||
range: null,
|
||||
documentVersion,
|
||||
cancellationToken,
|
||||
previousResultId);
|
||||
|
||||
var combinedSemanticRanges = CombineSemanticRanges(razorSemanticRanges, csharpSemanticRanges);
|
||||
|
||||
// We return null when we have an incomplete view of the document.
|
||||
// Likely CSharp ahead of us in terms of document versions.
|
||||
// We return null (which to the LSP is a no-op) to prevent flashing of CSharp elements.
|
||||
if (combinedSemanticRanges is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var newResultId = Guid.NewGuid().ToString();
|
||||
var newTokens = ConvertSemanticRangesToSemanticTokens(
|
||||
combinedSemanticRanges, codeDocument, newResultId, isFinalizedCSharp);
|
||||
UpdateRazorDocCache(textDocumentIdentifier.Uri, semanticVersion, newResultId, newTokens);
|
||||
|
||||
if (previousResults is null)
|
||||
{
|
||||
return new SemanticTokens { ResultId = newTokens.ResultId, Data = newTokens.Data.ToImmutableArray() };
|
||||
}
|
||||
|
||||
var razorSemanticEdits = SemanticTokensEditsDiffer.ComputeSemanticTokensEdits(newTokens, previousResults);
|
||||
return razorSemanticEdits;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new SemanticTokensFullOrDelta(new SemanticTokensDelta
|
||||
{
|
||||
Edits = Array.Empty<SemanticTokensEdit>(),
|
||||
ResultId = previousResultId,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRazorDocCache(
|
||||
DocumentUri documentUri,
|
||||
VersionStamp semanticVersion,
|
||||
string newResultId,
|
||||
SemanticTokensResponse newTokens)
|
||||
{
|
||||
// Update the tokens cache associated with the given Razor doc. If the doc has no
|
||||
// associated cache, we will create one.
|
||||
if (!_razorDocTokensCache.TryGetValue(documentUri, out var documentCache))
|
||||
{
|
||||
documentCache = new MemoryCache<string, VersionedSemanticTokens>(sizeLimit: MaxCachesPerDoc);
|
||||
_razorDocTokensCache.Set(documentUri, documentCache);
|
||||
}
|
||||
|
||||
documentCache.Set(newResultId, new VersionedSemanticTokens(semanticVersion, newTokens.Data, newTokens.IsFinalizedCSharp));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static async Task<RazorCodeDocument?> GetRazorCodeDocumentAsync(DocumentSnapshot documentSnapshot)
|
||||
|
@ -318,30 +189,33 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
}
|
||||
|
||||
// Internal and virtual for testing only
|
||||
internal virtual async Task<VersionedSemanticRange> GetCSharpSemanticRangesAsync(
|
||||
internal virtual async Task<SemanticRangeResponse> GetCSharpSemanticRangesAsync(
|
||||
RazorCodeDocument codeDocument,
|
||||
TextDocumentIdentifier textDocumentIdentifier,
|
||||
Range? range,
|
||||
Range razorRange,
|
||||
long documentVersion,
|
||||
CancellationToken cancellationToken,
|
||||
string? previousResultId = null)
|
||||
{
|
||||
var razorRanges = new List<SemanticRange>();
|
||||
|
||||
if (!TryGetMinimalCSharpRange(codeDocument, out var csharpRange))
|
||||
// We'll try to call into the mapping service to map to the projected range for us. If that doesn't work,
|
||||
// we'll try to find the minimal range ourselves.
|
||||
if (!_documentMappingService.TryMapToProjectedDocumentRange(codeDocument, razorRange, out var csharpRange) &&
|
||||
!TryGetMinimalCSharpRange(codeDocument, razorRange, out csharpRange))
|
||||
{
|
||||
return new VersionedSemanticRange(razorRanges, IsFinalizedCSharp: true);
|
||||
return SemanticRangeResponse.Default;
|
||||
}
|
||||
|
||||
var csharpResponse = await GetMatchingCSharpResponseAsync(textDocumentIdentifier, documentVersion, csharpRange, cancellationToken);
|
||||
|
||||
// Indicates an issue with retrieving the C# response (e.g. no response or C# is out of sync with us).
|
||||
// Unrecoverable, return null to indicate no change. It will retry in a bit.
|
||||
// Unrecoverable, return default to indicate no change. It will retry in a bit.
|
||||
if (csharpResponse is null)
|
||||
{
|
||||
return VersionedSemanticRange.Default;
|
||||
return SemanticRangeResponse.Default;
|
||||
}
|
||||
|
||||
var razorRanges = new List<SemanticRange>();
|
||||
|
||||
SemanticRange? previousSemanticRange = null;
|
||||
for (var i = 0; i < csharpResponse.Data.Length; i += 5)
|
||||
{
|
||||
|
@ -355,61 +229,66 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
lineDelta, charDelta, length, tokenType, tokenModifiers, previousSemanticRange);
|
||||
if (_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, semanticRange.Range, out var originalRange))
|
||||
{
|
||||
var razorRange = new SemanticRange(semanticRange.Kind, originalRange, tokenModifiers);
|
||||
if (range is null || range.OverlapsWith(razorRange.Range))
|
||||
var razorSemanticRange = new SemanticRange(semanticRange.Kind, originalRange, tokenModifiers);
|
||||
if (razorRange is null || razorRange.OverlapsWith(razorSemanticRange.Range))
|
||||
{
|
||||
razorRanges.Add(razorRange);
|
||||
razorRanges.Add(razorSemanticRange);
|
||||
}
|
||||
}
|
||||
|
||||
previousSemanticRange = semanticRange;
|
||||
}
|
||||
|
||||
var result = razorRanges.ToImmutableList();
|
||||
return new VersionedSemanticRange(result, csharpResponse.IsFinalizedCSharp);
|
||||
var result = razorRanges.ToArray();
|
||||
var semanticRanges = new SemanticRangeResponse(result, IsCSharpFinalized: csharpResponse.IsCSharpFinalized);
|
||||
return semanticRanges;
|
||||
}
|
||||
|
||||
// Internal for testing only
|
||||
internal static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, [NotNullWhen(true)] out Range? range)
|
||||
private static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, Range razorRange, [NotNullWhen(true)] out Range? csharpRange)
|
||||
{
|
||||
var minIndex = -1;
|
||||
var maxIndex = -1;
|
||||
SourceSpan? minGeneratedSpan = null;
|
||||
SourceSpan? maxGeneratedSpan = null;
|
||||
|
||||
var sourceText = codeDocument.GetSourceText();
|
||||
var textSpan = razorRange.AsTextSpan(sourceText);
|
||||
var csharpDoc = codeDocument.GetCSharpDocument();
|
||||
|
||||
// If there aren't any source mappings, there's no C# code in the Razor doc.
|
||||
if (csharpDoc.SourceMappings.Count == 0)
|
||||
{
|
||||
range = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We only need to colorize the portions of the generated doc that correspond with a C# mapping.
|
||||
// To accomplish this while minimizing the amount of work we need to do, we'll only colorize the
|
||||
// range spanning from the first C# span to the last C# span.
|
||||
SourceSpan? minSpan = null;
|
||||
SourceSpan? maxSpan = null;
|
||||
|
||||
// We want to find the min and max C# source mapping that corresponds with our Razor range.
|
||||
foreach (var mapping in csharpDoc.SourceMappings)
|
||||
{
|
||||
var generatedSpan = mapping.GeneratedSpan;
|
||||
if (minSpan is null || generatedSpan.AbsoluteIndex < minSpan.Value.AbsoluteIndex)
|
||||
{
|
||||
minSpan = generatedSpan;
|
||||
}
|
||||
var mappedTextSpan = mapping.OriginalSpan.AsTextSpan();
|
||||
|
||||
if (maxSpan is null || generatedSpan.AbsoluteIndex + generatedSpan.Length > maxSpan.Value.AbsoluteIndex + maxSpan.Value.Length)
|
||||
if (textSpan.OverlapsWith(mappedTextSpan))
|
||||
{
|
||||
maxSpan = generatedSpan;
|
||||
if (minIndex == -1 || mapping.OriginalSpan.AbsoluteIndex < minIndex)
|
||||
{
|
||||
minIndex = mapping.OriginalSpan.AbsoluteIndex;
|
||||
minGeneratedSpan = mapping.GeneratedSpan;
|
||||
}
|
||||
|
||||
if (maxIndex == -1 || mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length > maxIndex)
|
||||
{
|
||||
maxIndex = mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length;
|
||||
maxGeneratedSpan = mapping.GeneratedSpan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var csharpSourceText = codeDocument.GetCSharpSourceText();
|
||||
var start = csharpSourceText.Lines.GetLinePosition(minSpan!.Value.AbsoluteIndex);
|
||||
var startPosition = new Position(start.Line, start.Character);
|
||||
// Create a new projected range based on our calculated min/max source spans.
|
||||
if (minGeneratedSpan is not null && maxGeneratedSpan is not null)
|
||||
{
|
||||
var csharpSourceText = codeDocument.GetCSharpSourceText();
|
||||
var startRange = minGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText);
|
||||
var endRange = maxGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText);
|
||||
|
||||
var end = csharpSourceText.Lines.GetLinePosition(maxSpan!.Value.AbsoluteIndex + maxSpan!.Value.Length);
|
||||
var endPosition = new Position(end.Line, end.Character);
|
||||
csharpRange = new Range { Start = startRange.Start, End = endRange.End };
|
||||
return true;
|
||||
}
|
||||
|
||||
range = new Range(startPosition, endPosition);
|
||||
return true;
|
||||
csharpRange = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<SemanticTokensResponse?> GetMatchingCSharpResponseAsync(
|
||||
|
@ -433,8 +312,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
return null;
|
||||
}
|
||||
|
||||
// C# doesn't return resultIds so we can return null here.
|
||||
return new SemanticTokensResponse(ResultId: null, csharpResponse.Tokens ?? Array.Empty<int>(), csharpResponse.IsFinalized);
|
||||
var response = new SemanticTokensResponse(csharpResponse.Tokens ?? Array.Empty<int>(), csharpResponse.IsFinalized);
|
||||
return response;
|
||||
}
|
||||
|
||||
private static SemanticRange DataToSemanticRange(
|
||||
|
@ -465,11 +344,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
return semanticRange;
|
||||
}
|
||||
|
||||
private static SemanticTokensResponse ConvertSemanticRangesToSemanticTokens(
|
||||
private static ImmutableArray<int> ConvertSemanticRangesToSemanticTokensData(
|
||||
IReadOnlyList<SemanticRange> semanticRanges,
|
||||
RazorCodeDocument razorCodeDocument,
|
||||
string? resultId,
|
||||
bool isFinalizedCSharp)
|
||||
RazorCodeDocument razorCodeDocument)
|
||||
{
|
||||
SemanticRange? previousResult = null;
|
||||
|
||||
|
@ -482,16 +359,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
previousResult = result;
|
||||
}
|
||||
|
||||
var tokensResult = new SemanticTokensResponse(resultId, data.ToArray(), isFinalizedCSharp);
|
||||
return tokensResult;
|
||||
}
|
||||
|
||||
private static async Task<VersionStamp> GetDocumentSemanticVersionAsync(DocumentSnapshot documentSnapshot)
|
||||
{
|
||||
var documentVersionStamp = await documentSnapshot.GetTextVersionAsync();
|
||||
var semanticVersion = documentVersionStamp.GetNewerVersion(documentSnapshot.Project.Version);
|
||||
|
||||
return semanticVersion;
|
||||
return data.ToImmutableArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -554,18 +422,17 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
}, cancellationToken);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal record VersionedSemanticRange(IReadOnlyList<SemanticRange>? SemanticRanges, bool IsFinalizedCSharp)
|
||||
private record SemanticTokensResponse(int[] Data, bool IsCSharpFinalized)
|
||||
{
|
||||
public static VersionedSemanticRange Default => new(null, false);
|
||||
public static SemanticTokensResponse Default => new(Array.Empty<int>(), false);
|
||||
}
|
||||
|
||||
private record VersionedSemanticTokens(VersionStamp? SemanticVersion, IReadOnlyList<int> SemanticTokens, bool IsFinalizedCSharp);
|
||||
|
||||
// Internal for testing
|
||||
internal record SemanticTokensResponse(string? ResultId, int[] Data, bool IsFinalizedCSharp)
|
||||
internal record SemanticRangeResponse(SemanticRange[]? SemanticRanges, bool IsCSharpFinalized)
|
||||
{
|
||||
public static SemanticTokensResponse Default => new(null, Array.Empty<int>(), false);
|
||||
public static SemanticRangeResponse Default => new(null, false);
|
||||
}
|
||||
|
||||
private record SemanticTokensCacheResponse(VersionStamp SemanticVersion, Range Range, SemanticTokens SemanticTokens, bool IsCSharpFinalized);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
{
|
||||
internal abstract class RazorSemanticTokensInfoService
|
||||
{
|
||||
public abstract Task<SemanticTokens?> GetSemanticTokensAsync(TextDocumentIdentifier textDocumentIdentifier, CancellationToken cancellationToken);
|
||||
|
||||
public abstract Task<SemanticTokens?> GetSemanticTokensAsync(TextDocumentIdentifier textDocumentIdentifier, Range? range, CancellationToken cancellationToken);
|
||||
|
||||
public abstract Task<SemanticTokensFullOrDelta?> GetSemanticTokensEditsAsync(TextDocumentIdentifier textDocumentIdentifier, string? previousId, CancellationToken cancellationToken);
|
||||
public abstract Task<SemanticTokens?> GetSemanticTokensAsync(TextDocumentIdentifier textDocumentIdentifier, Range range, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using static Microsoft.AspNetCore.Razor.LanguageServer.Semantic.DefaultRazorSemanticTokensInfoService;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
||||
{
|
||||
internal class SemanticTokensEditsDiffer : TextDiffer
|
||||
{
|
||||
private SemanticTokensEditsDiffer(IReadOnlyList<int> oldArray, int[] newArray)
|
||||
{
|
||||
if (oldArray is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(oldArray));
|
||||
}
|
||||
|
||||
OldArray = oldArray;
|
||||
NewArray = newArray;
|
||||
}
|
||||
|
||||
private IReadOnlyList<int> OldArray { get; }
|
||||
private int[] NewArray { get; }
|
||||
|
||||
protected override int OldTextLength => OldArray.Count;
|
||||
protected override int NewTextLength => NewArray.Length;
|
||||
|
||||
protected override bool ContentEquals(int oldTextIndex, int newTextIndex)
|
||||
{
|
||||
return OldArray[oldTextIndex] == NewArray[newTextIndex];
|
||||
}
|
||||
|
||||
public static SemanticTokensFullOrDelta ComputeSemanticTokensEdits(
|
||||
SemanticTokensResponse newTokens,
|
||||
IReadOnlyList<int> previousResults)
|
||||
{
|
||||
var differ = new SemanticTokensEditsDiffer(previousResults, newTokens.Data);
|
||||
var diffs = differ.ComputeDiff();
|
||||
var edits = differ.ProcessEdits(diffs);
|
||||
var result = new SemanticTokensDelta
|
||||
{
|
||||
ResultId = newTokens.ResultId,
|
||||
Edits = edits,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Container<SemanticTokensEdit> ProcessEdits(IReadOnlyList<DiffEdit> diffs)
|
||||
{
|
||||
var razorResults = new List<RazorSemanticTokensEdit>();
|
||||
foreach (var diff in diffs)
|
||||
{
|
||||
var current = razorResults.Count > 0 ? razorResults[razorResults.Count - 1] : null;
|
||||
switch (diff.Operation)
|
||||
{
|
||||
case DiffEdit.Type.Delete:
|
||||
if (current != null &&
|
||||
current.Start + current.DeleteCount == diff.Position)
|
||||
{
|
||||
current.DeleteCount += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
razorResults.Add(new RazorSemanticTokensEdit
|
||||
{
|
||||
Start = diff.Position,
|
||||
DeleteCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case DiffEdit.Type.Insert:
|
||||
if (current != null &&
|
||||
current.Data != null &&
|
||||
current.Data.Count > 0 &&
|
||||
current.Start == diff.Position)
|
||||
{
|
||||
current.Data.Add(NewArray[diff.NewTextPosition!.Value]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var semanticTokensEdit = new RazorSemanticTokensEdit
|
||||
{
|
||||
Start = diff.Position,
|
||||
Data = new List<int>
|
||||
{
|
||||
NewArray[diff.NewTextPosition!.Value],
|
||||
},
|
||||
DeleteCount = 0,
|
||||
};
|
||||
razorResults.Add(semanticTokensEdit);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var results = razorResults.Select(e => e.ToSemanticTokensEdit());
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
// We need to have a shim class because SemanticTokensEdit.Data is Immutable, so if we operate on it directly then every time we append an element we're allocating an entire new array.
|
||||
// In some large (but not implausibly so) copy-paste scenarios that can cause long delays and large allocations.
|
||||
private class RazorSemanticTokensEdit
|
||||
{
|
||||
public int Start { get; set; }
|
||||
public int DeleteCount { get; set; }
|
||||
public IList<int>? Data { get; set; }
|
||||
|
||||
// Since we need to add to the Data object during ProcessEdits but return an "ImmutableArray" in the end lets wait until the end to convert.
|
||||
public SemanticTokensEdit ToSemanticTokensEdit()
|
||||
{
|
||||
return new SemanticTokensEdit
|
||||
{
|
||||
Data = Data?.ToImmutableArray(),
|
||||
Start = Start,
|
||||
DeleteCount = DeleteCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Razor.Language.Components;
|
|||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic.Models;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
||||
|
@ -17,18 +18,23 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
{
|
||||
private readonly List<SemanticRange> _semanticRanges;
|
||||
private readonly RazorCodeDocument _razorCodeDocument;
|
||||
private readonly Range? _range;
|
||||
|
||||
private TagHelperSemanticRangeVisitor(RazorCodeDocument razorCodeDocument, Range? range)
|
||||
private TagHelperSemanticRangeVisitor(RazorCodeDocument razorCodeDocument, TextSpan? range) : base(range)
|
||||
{
|
||||
_semanticRanges = new List<SemanticRange>();
|
||||
_razorCodeDocument = razorCodeDocument;
|
||||
_range = range;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<SemanticRange> VisitAllNodes(RazorCodeDocument razorCodeDocument, Range? range = null)
|
||||
{
|
||||
var visitor = new TagHelperSemanticRangeVisitor(razorCodeDocument, range);
|
||||
TextSpan? rangeAsTextSpan = null;
|
||||
if (range is not null)
|
||||
{
|
||||
var sourceText = razorCodeDocument.GetSourceText();
|
||||
rangeAsTextSpan = range.AsRazorTextSpan(sourceText);
|
||||
}
|
||||
|
||||
var visitor = new TagHelperSemanticRangeVisitor(razorCodeDocument, rangeAsTextSpan);
|
||||
|
||||
visitor.Visit(razorCodeDocument.GetSyntaxTree().Root);
|
||||
|
||||
|
@ -478,12 +484,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic
|
|||
|
||||
void AddRange(SemanticRange semanticRange)
|
||||
{
|
||||
if (_range is null || semanticRange.Range.OverlapsWith(_range))
|
||||
if (semanticRange.Range.Start != semanticRange.Range.End)
|
||||
{
|
||||
if (semanticRange.Range.Start != semanticRange.Range.End)
|
||||
{
|
||||
_semanticRanges.Add(semanticRange);
|
||||
}
|
||||
_semanticRanges.Add(semanticRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
@ -21,7 +23,6 @@ using OmniSharp.Extensions.JsonRpc;
|
|||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using Xunit;
|
||||
using OmniSharpRange = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
|
||||
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Semantic
|
||||
{
|
||||
|
@ -45,21 +46,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Semantic
|
|||
await AssertSemanticTokensAsync(txt, isRazor: false, csharpTokens: cSharpResponse, documentMappings: null, documentVersion: 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokensEdits_CSharp_RazorIfNotReady()
|
||||
{
|
||||
var txt = $@"<p></p>@{{
|
||||
var d = ""t"";
|
||||
}}";
|
||||
|
||||
var cSharpResponse = new ProvideSemanticTokensResponse(
|
||||
tokens: Array.Empty<int>(), isFinalized: true, hostDocumentSyncVersion: 0);
|
||||
var isRazor = true;
|
||||
|
||||
var response = await AssertSemanticTokensAsync(txt, isRazor, csharpTokens: cSharpResponse, documentVersion: 0);
|
||||
_ = await AssertSemanticTokenEditsAsync(txt, expectDelta: true, isRazor, response.Item1, response.Item2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_CSharpBlock_HTML()
|
||||
{
|
||||
|
@ -278,68 +264,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Semantic
|
|||
|
||||
await AssertSemanticTokensAsync(txt, isRazor: false, csharpTokens: cSharpResponse, documentMappings: mappings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_CSharp_UsesCache()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}@{{ var d = }}";
|
||||
|
||||
var csharpTokens = new int[]
|
||||
{
|
||||
14, 12, 3, RazorSemanticTokensLegend.CSharpKeyword, 0,
|
||||
13, 15, 1, RazorSemanticTokensLegend.CSharpVariable, 0,
|
||||
12, 25, 1, RazorSemanticTokensLegend.CSharpOperator, 0,
|
||||
11, 10, 25, RazorSemanticTokensLegend.CSharpKeyword, 0, // No mapping
|
||||
};
|
||||
|
||||
var cSharpResponse = new ProvideSemanticTokensResponse(csharpTokens, isFinalized: true, hostDocumentSyncVersion: 0);
|
||||
|
||||
var mappings = new (OmniSharpRange, OmniSharpRange?)[] {
|
||||
(new OmniSharpRange(new Position(14, 12), new Position(14, 15)), new OmniSharpRange(new Position(1, 3), new Position(1, 6))),
|
||||
(new OmniSharpRange(new Position(27, 15), new Position(27, 16)), new OmniSharpRange(new Position(1, 7), new Position(1, 8))),
|
||||
(new OmniSharpRange(new Position(39, 25), new Position(39, 26)), new OmniSharpRange(new Position(1, 9), new Position(1, 10))),
|
||||
(new OmniSharpRange(new Position(50, 10), new Position(50, 35)), null)
|
||||
};
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, mockClient, document) = await AssertSemanticTokensAsync(txt, isRazor, csharpTokens: cSharpResponse, documentMappings: mappings);
|
||||
|
||||
await AssertSemanticTokenEditsAsync(txt: null, expectDelta: true, isRazor, previousResultId: previousResultId, service: service);
|
||||
mockClient.Verify(l => l.SendRequestAsync(LanguageServerConstants.RazorProvideSemanticTokensRangeEndpoint, It.IsAny<SemanticTokensParams>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_CSharp_RequeueOnPartialTokens()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}@{{ var d = }}";
|
||||
|
||||
var csharpTokens = new int[]
|
||||
{
|
||||
14, 12, 3, RazorSemanticTokensLegend.CSharpKeyword, 0,
|
||||
13, 15, 1, RazorSemanticTokensLegend.CSharpVariable, 0,
|
||||
12, 25, 1, RazorSemanticTokensLegend.CSharpOperator, 0,
|
||||
11, 10, 25, RazorSemanticTokensLegend.CSharpKeyword, 0, // No mapping
|
||||
};
|
||||
|
||||
var csharpResponse = new ProvideSemanticTokensResponse(csharpTokens, isFinalized: false, hostDocumentSyncVersion: 0);
|
||||
|
||||
var mappings = new (OmniSharpRange, OmniSharpRange?)[] {
|
||||
(new OmniSharpRange(new Position(14, 12), new Position(14, 15)), new OmniSharpRange(new Position(1, 3), new Position(1, 6))),
|
||||
(new OmniSharpRange(new Position(27, 15), new Position(27, 16)), new OmniSharpRange(new Position(1, 7), new Position(1, 8))),
|
||||
(new OmniSharpRange(new Position(39, 25), new Position(39, 26)), new OmniSharpRange(new Position(1, 9), new Position(1, 10))),
|
||||
(new OmniSharpRange(new Position(50, 10), new Position(50, 35)), null)
|
||||
};
|
||||
|
||||
var csharpFinalizedResponse = new ProvideSemanticTokensResponse(
|
||||
tokens: Array.Empty<int>(), isFinalized: true, hostDocumentSyncVersion: 0);
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, mockClient, document) = await AssertSemanticTokensAsync(
|
||||
txt, isRazor, csharpTokens: csharpResponse, documentMappings: mappings);
|
||||
|
||||
await AssertSemanticTokenEditsAsync(txt: null, expectDelta: true, isRazor, previousResultId, service: service);
|
||||
mockClient.Verify(l => l.SendRequestAsync(LanguageServerConstants.RazorProvideSemanticTokensRangeEndpoint, It.IsAny<ProvideSemanticTokensRangeParams>()), Times.Exactly(2));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HTML
|
||||
|
@ -547,12 +471,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Semantic
|
|||
await AssertSemanticTokensAsync(txt, isRazor: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/dotnet/razor-tooling/issues/5948")]
|
||||
public async Task GetSemanticTokens_Razor_InRangeAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var startIndex = txt.IndexOf("test1", StringComparison.Ordinal);
|
||||
var startIndex = txt.IndexOf("test1", StringComparison.Ordinal); ;
|
||||
var endIndex = startIndex + 5;
|
||||
|
||||
var codeDocument = CreateCodeDocument(txt, DefaultTagHelpers);
|
||||
|
@ -564,7 +488,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Semantic
|
|||
var endPosition = new Position(endLine, endChar);
|
||||
var location = new OmniSharpRange(startPosition, endPosition);
|
||||
|
||||
await AssertSemanticTokensAsync(txt, isRazor: false, location: location);
|
||||
await AssertSemanticTokensAsync(txt, isRazor: false, range: location);
|
||||
}
|
||||
#endregion DirectiveAttributes
|
||||
|
||||
|
@ -665,190 +589,29 @@ slf*@";
|
|||
await AssertSemanticTokensAsync(txt, isRazor: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_EditTwiceAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1>";
|
||||
var secondTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1>T</test1>";
|
||||
var thirdTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1>Test</test1>";
|
||||
var isRazor = true;
|
||||
var (firstResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, secondTxt, thirdTxt }, new bool[] { isRazor, isRazor, isRazor });
|
||||
|
||||
var (secondResultId, _, _) = await AssertSemanticTokenEditsAsync(secondTxt, expectDelta: true, isRazor, previousResultId: firstResultId, service);
|
||||
|
||||
var (_, _, _) = await AssertSemanticTokenEditsAsync(thirdTxt, expectDelta: true, isRazor, previousResultId: secondResultId, service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_NoDifferenceAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(txt, isRazor);
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(txt, expectDelta: true, isRazor, previousResultId: previousResultId, service: service);
|
||||
Assert.Equal(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_RemoveTokensAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1><test1></test1><test1></test1> ";
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_OnlyDifferences_AppendAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1 bool-val='true'></test1> ";
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service: service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_CoalesceDeleteAndAddAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1 /> ";
|
||||
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}{Environment.NewLine}<p @minimized /> ";
|
||||
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { false, true });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor: false, previousResultId: previousResultId, service: service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_OriginallyNone_ThenSomeAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}";
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_GetEditsWithNoPreviousAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var (previousResultId, _, _) = await AssertSemanticTokenEditsAsync(txt, expectDelta: false, isRazor: false, previousResultId: null);
|
||||
Assert.NotNull(previousResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_SomeTagHelpers_ThenNoneAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<p></p> ";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_OnlyDifferences_InternalAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1><test1></test1> ";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_ModifyAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}" +
|
||||
$"<test1 bool-val=\"true\" />{Environment.NewLine}" +
|
||||
$"<test1 bool-val=\"true\" />{Environment.NewLine}" +
|
||||
$"<test1 bool-val=\"true\" />{Environment.NewLine}";
|
||||
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}" +
|
||||
$"<test1 bool-va=\"true\" />{Environment.NewLine}" +
|
||||
$"<test1 bool-val=\"true\" />{Environment.NewLine}" +
|
||||
$"<test1 bool-val=\"true\" />{Environment.NewLine}";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service: service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_Razor_OnlyDifferences_NewLinesAsync()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1> ";
|
||||
var newTxt = $"@addTagHelper *, TestAssembly{Environment.NewLine}<test1></test1>{Environment.NewLine}" +
|
||||
$"<test1></test1> ";
|
||||
|
||||
var isRazor = false;
|
||||
var (previousResultId, service, _, _) = await AssertSemanticTokensAsync(new string[] { txt, newTxt }, new bool[] { isRazor, isRazor });
|
||||
|
||||
var (newResultId, _, _) = await AssertSemanticTokenEditsAsync(newTxt, expectDelta: true, isRazor, previousResultId: previousResultId, service);
|
||||
Assert.NotEqual(previousResultId, newResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSemanticTokens_CSharp_TryGetMinimalCSharpRange()
|
||||
{
|
||||
var txt = $"@addTagHelper *, TestAssembly{Environment.NewLine}@{{ var d = }}";
|
||||
var (documentSnapshots, _) = CreateDocumentSnapshot(new string[] { txt }, new bool[] { false }, DefaultTagHelpers);
|
||||
|
||||
var snapshot = documentSnapshots.Dequeue();
|
||||
var csharpDoc = await snapshot.GetGeneratedOutputAsync();
|
||||
DefaultRazorSemanticTokensInfoService.TryGetMinimalCSharpRange(csharpDoc, out var actualRange);
|
||||
|
||||
var expectedRange = new Range
|
||||
{
|
||||
Start = new Position(line: 12, character: 38),
|
||||
End = new Position(line: 29, character: 11)
|
||||
};
|
||||
|
||||
Assert.Equal(expectedRange, actualRange);
|
||||
}
|
||||
|
||||
private Task<(string?, RazorSemanticTokensInfoService, Mock<ClientNotifierServiceBase>, Queue<DocumentSnapshot>)> AssertSemanticTokensAsync(
|
||||
string txt,
|
||||
bool isRazor,
|
||||
OmniSharpRange? range = null,
|
||||
RazorSemanticTokensInfoService? service = null,
|
||||
OmniSharpRange? location = null,
|
||||
ProvideSemanticTokensResponse? csharpTokens = null,
|
||||
(OmniSharpRange, OmniSharpRange?)[]? documentMappings = null,
|
||||
int? documentVersion = 0)
|
||||
{
|
||||
return AssertSemanticTokensAsync(new string[] { txt }, new bool[] { isRazor }, service, location, csharpTokens, documentMappings, documentVersion);
|
||||
if (range is null)
|
||||
{
|
||||
var lines = txt.Split(Environment.NewLine);
|
||||
range = new OmniSharpRange { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = lines.Length - 1, Character = lines[^1].Length } };
|
||||
};
|
||||
|
||||
return AssertSemanticTokensAsync(new string[] { txt }, new bool[] { isRazor }, range, service, csharpTokens, documentMappings, documentVersion);
|
||||
}
|
||||
|
||||
private async Task<(string?, RazorSemanticTokensInfoService, Mock<ClientNotifierServiceBase>, Queue<DocumentSnapshot>)> AssertSemanticTokensAsync(
|
||||
string[] txtArray,
|
||||
bool[] isRazorArray,
|
||||
OmniSharpRange range,
|
||||
RazorSemanticTokensInfoService? service = null,
|
||||
OmniSharpRange? location = null,
|
||||
ProvideSemanticTokensResponse? csharpTokens = null,
|
||||
(OmniSharpRange, OmniSharpRange?)[]? documentMappings = null,
|
||||
int? documentVersion = 0)
|
||||
|
@ -869,7 +632,8 @@ slf*@";
|
|||
|
||||
if (service is null)
|
||||
{
|
||||
(service, serviceMock) = GetDefaultRazorSemanticTokenInfoService(documentSnapshots, csharpTokens, documentMappings, documentVersion);
|
||||
(service, serviceMock) = await GetDefaultRazorSemanticTokenInfoServiceAsync(
|
||||
documentSnapshots, csharpTokens, documentMappings, documentVersion).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var outService = service;
|
||||
|
@ -877,7 +641,7 @@ slf*@";
|
|||
var textDocumentIdentifier = textDocumentIdentifiers.Dequeue();
|
||||
|
||||
// Act
|
||||
var tokens = await service.GetSemanticTokensAsync(textDocumentIdentifier, location, CancellationToken.None);
|
||||
var tokens = await service.GetSemanticTokensAsync(textDocumentIdentifier, range, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
AssertSemanticTokensMatchesBaseline(tokens?.Data);
|
||||
|
@ -885,45 +649,7 @@ slf*@";
|
|||
return (tokens?.ResultId, outService, serviceMock!, documentSnapshots);
|
||||
}
|
||||
|
||||
private async Task<(string?, RazorSemanticTokensInfoService, Mock<ClientNotifierServiceBase>)> AssertSemanticTokenEditsAsync(
|
||||
string? txt,
|
||||
bool expectDelta,
|
||||
bool isRazor,
|
||||
string? previousResultId,
|
||||
RazorSemanticTokensInfoService? service = null,
|
||||
long? documentVersion = 0)
|
||||
{
|
||||
// Arrange
|
||||
var cSharpTokens = new ProvideSemanticTokensResponse(tokens: null, isFinalized: true, documentVersion);
|
||||
|
||||
Mock<ClientNotifierServiceBase>? clientMock = null;
|
||||
if (service is null)
|
||||
{
|
||||
var (documentSnapshots, _) = CreateDocumentSnapshot(new string?[] { txt }, new bool[] { isRazor }, DefaultTagHelpers);
|
||||
(service, clientMock) = GetDefaultRazorSemanticTokenInfoService(documentSnapshots, cSharpTokens);
|
||||
}
|
||||
|
||||
var textDocumentIdentifier = GetIdentifier(isRazor);
|
||||
|
||||
// Act
|
||||
var edits = await service.GetSemanticTokensEditsAsync(textDocumentIdentifier, previousResultId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
if (expectDelta)
|
||||
{
|
||||
AssertSemanticTokensEditsMatchesBaseline(edits!);
|
||||
|
||||
return (edits!.Delta!.ResultId, service, clientMock!);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertSemanticTokensMatchesBaseline(edits!.Full!.Data);
|
||||
|
||||
return (edits.Full.ResultId, service, clientMock!);
|
||||
}
|
||||
}
|
||||
|
||||
private (RazorSemanticTokensInfoService, Mock<ClientNotifierServiceBase>) GetDefaultRazorSemanticTokenInfoService(
|
||||
private async Task<(RazorSemanticTokensInfoService, Mock<ClientNotifierServiceBase>)> GetDefaultRazorSemanticTokenInfoServiceAsync(
|
||||
Queue<DocumentSnapshot> documentSnapshots,
|
||||
ProvideSemanticTokensResponse? csharpTokens = null,
|
||||
(OmniSharpRange, OmniSharpRange?)[]? documentMappings = null,
|
||||
|
@ -939,7 +665,7 @@ slf*@";
|
|||
.Setup(l => l.SendRequestAsync(LanguageServerConstants.RazorProvideSemanticTokensRangeEndpoint, It.IsAny<SemanticTokensParams>()))
|
||||
.Returns(Task.FromResult(responseRouterReturns.Object));
|
||||
var documentMappingService = new Mock<RazorDocumentMappingService>(MockBehavior.Strict);
|
||||
if (documentMappings != null)
|
||||
if (documentMappings is not null)
|
||||
{
|
||||
foreach (var (cSharpRange, razorRange) in documentMappings)
|
||||
{
|
||||
|
@ -950,6 +676,25 @@ slf*@";
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var snapshot in documentSnapshots)
|
||||
{
|
||||
var codeDocument = await snapshot.GetGeneratedOutputAsync().ConfigureAwait(false);
|
||||
if (codeDocument is not null)
|
||||
{
|
||||
var sourceText = codeDocument.GetSourceText();
|
||||
var lastLine = sourceText.Lines.Last();
|
||||
var projectedRange = new OmniSharpRange
|
||||
{
|
||||
Start = new Position(0, 0),
|
||||
End = new Position(sourceText.Lines.Count - 1, character: lastLine.Span.Length),
|
||||
};
|
||||
|
||||
documentMappingService
|
||||
.Setup(s => s.TryMapToProjectedDocumentRange(codeDocument, It.IsAny<OmniSharpRange>(), out projectedRange))
|
||||
.Returns(true);
|
||||
}
|
||||
}
|
||||
|
||||
var loggingFactory = new Mock<LoggerFactory>(MockBehavior.Strict);
|
||||
loggingFactory.Protected().Setup("CheckDisposed").CallBase();
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 91 0 //markupTagDelimiter
|
||||
0 1 1 92 0 //markupElement
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 92 0 //markupElement
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 84 0 //razorTransition
|
||||
0 1 1 84 0 //razorTransition
|
||||
2 0 1 84 0 //razorTransition
|
|
@ -1 +0,0 @@
|
|||
Delta
|
|
@ -1,9 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 84 0 //razorTransition
|
||||
0 1 1 84 0 //razorTransition
|
||||
0 2 3 15 0 //keyword
|
||||
0 4 1 8 0 //variable
|
||||
0 2 1 21 0 //operator
|
||||
0 2 1 84 0 //razorTransition
|
|
@ -1 +0,0 @@
|
|||
Delta
|
|
@ -1,9 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 84 0 //razorTransition
|
||||
0 1 1 84 0 //razorTransition
|
||||
0 2 3 15 0 //keyword
|
||||
0 4 1 8 0 //variable
|
||||
0 2 1 21 0 //operator
|
||||
0 2 1 84 0 //razorTransition
|
|
@ -1 +0,0 @@
|
|||
Delta
|
|
@ -1,7 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 6 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
|
@ -1,7 +0,0 @@
|
|||
Delta
|
||||
10 0 [ 2 0 ]
|
||||
11 0 [ 91 ]
|
||||
12 0 [ 0 1 ]
|
||||
13 1 [ 92 0 0 2 1 84 ]
|
||||
17 2 [ 9 86 ]
|
||||
21 1 [ 10 ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 92 0 //markupElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 92 0 //markupElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
26 1 [ 2 ]
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
26 1 [ 5 ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,27 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 6 8 83 0 //razorTagHelperAttribute
|
||||
0 8 1 93 0 //markupOperator
|
||||
0 1 1 95 0 //markupAttributeQuote
|
||||
0 5 1 95 0 //markupAttributeQuote
|
||||
0 2 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 6 8 83 0 //razorTagHelperAttribute
|
||||
0 8 1 93 0 //markupOperator
|
||||
0 1 1 95 0 //markupAttributeQuote
|
||||
0 5 1 95 0 //markupAttributeQuote
|
||||
0 2 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 6 8 83 0 //razorTagHelperAttribute
|
||||
0 8 1 93 0 //markupOperator
|
||||
0 1 1 95 0 //markupAttributeQuote
|
||||
0 5 1 95 0 //markupAttributeQuote
|
||||
0 2 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
|
@ -1,4 +0,0 @@
|
|||
Delta
|
||||
22 2 [ 7 94 ]
|
||||
26 1 [ 7 ]
|
||||
36 1 [ 1 4 95 0 0 4 ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1 +0,0 @@
|
|||
Delta
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,3 +0,0 @@
|
|||
Delta
|
||||
21 0 [ 6 8 83 0 0 8 1 93 0 0 1 1 95 0 0 ]
|
||||
22 0 [ 1 95 0 0 1 ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
45 0 [ 0 1 1 91 0 0 1 5 82 0 0 5 1 91 0 0 1 1 91 0 0 1 1 91 0 0 1 5 82 0 0 5 1 91 0 ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
45 0 [ 1 0 1 91 0 0 1 5 82 0 0 5 1 91 0 0 1 1 91 0 0 1 1 91 0 0 1 5 82 0 0 5 1 91 0 ]
|
|
@ -1,3 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
10 0 [ 1 0 1 91 0 0 1 5 82 0 0 5 1 91 0 0 1 1 91 0 0 1 1 91 0 0 1 5 82 0 0 5 1 91 0 ]
|
|
@ -1,24 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,2 +0,0 @@
|
|||
Delta
|
||||
45 70 [ ]
|
|
@ -1,10 +0,0 @@
|
|||
//line,characterPos,length,tokenType,modifier
|
||||
0 0 1 84 0 //razorTransition
|
||||
0 1 12 87 0 //razorDirective
|
||||
1 0 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 1 91 0 //markupTagDelimiter
|
||||
0 1 5 82 0 //razorTagHelperElement
|
||||
0 5 1 91 0 //markupTagDelimiter
|
|
@ -1,5 +0,0 @@
|
|||
Delta
|
||||
17 2 [ 1 92 ]
|
||||
21 1 [ 1 ]
|
||||
37 2 [ 1 92 ]
|
||||
41 1 [ 1 ]
|
Загрузка…
Ссылка в новой задаче