private static SemanticTokens ConvertSemanticRangesToSemanticTokens( IReadOnlyList <SemanticRange> semanticRanges, RazorCodeDocument razorCodeDocument) { if (semanticRanges is null) { return(null); } SemanticRange previousResult = null; var data = new List <uint>(); foreach (var result in semanticRanges) { var newData = GetData(result, previousResult, razorCodeDocument); data.AddRange(newData); previousResult = result; } var resultId = Guid.NewGuid(); var tokensResult = new SemanticTokens { Data = data.ToArray(), ResultId = resultId.ToString() }; _semanticTokensCache.Set(resultId.ToString(), data); return(tokensResult); }
private void AddNode(SemanticRange semanticRange) { if (_range is null || semanticRange.Range.OverlapsWith(_range)) { _semanticRanges.Add(semanticRange); } }
private static SemanticRange DataToSemanticRange( int lineDelta, int charDelta, int length, int tokenType, int tokenModifiers, SemanticRange?previousSemanticRange = null) { if (previousSemanticRange is null) { previousSemanticRange = new SemanticRange(0, new Range(new Position(0, 0), new Position(0, 0)), modifier: 0); } var startLine = previousSemanticRange.Range.End.Line + lineDelta; var previousEndChar = lineDelta == 0 ? previousSemanticRange.Range.Start.Character : 0; var startCharacter = previousEndChar + charDelta; var start = new Position(startLine, startCharacter); var endLine = startLine; var endCharacter = startCharacter + length; var end = new Position(endLine, endCharacter); var range = new Range(start, end); var semanticRange = new SemanticRange(tokenType, range, tokenModifiers); return(semanticRange); }
// Internal and virtual for testing only internal virtual async Task <SemanticRange[]?> GetCSharpSemanticRangesAsync( RazorCodeDocument codeDocument, TextDocumentIdentifier textDocumentIdentifier, Range razorRange, long documentVersion, CancellationToken cancellationToken, string?previousResultId = null) { // 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)) { // There's no C# in the range. return(Array.Empty <SemanticRange>()); } 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 default to indicate no change. We've already queued up a refresh request in // `GetMatchingCSharpResponseAsync` that will cause us to retry in a bit. if (csharpResponse is null) { _logger.LogWarning($"Issue with retrieving C# response for Razor range: {razorRange}"); return(null); } var razorRanges = new List <SemanticRange>(); SemanticRange?previousSemanticRange = null; for (var i = 0; i < csharpResponse.Length; i += TokenSize) { var lineDelta = csharpResponse[i]; var charDelta = csharpResponse[i + 1]; var length = csharpResponse[i + 2]; var tokenType = csharpResponse[i + 3]; var tokenModifiers = csharpResponse[i + 4]; var semanticRange = DataToSemanticRange( lineDelta, charDelta, length, tokenType, tokenModifiers, previousSemanticRange); if (_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, semanticRange.Range, out var originalRange)) { var razorSemanticRange = new SemanticRange(semanticRange.Kind, originalRange, tokenModifiers); if (razorRange is null || razorRange.OverlapsWith(razorSemanticRange.Range)) { razorRanges.Add(razorSemanticRange); } } previousSemanticRange = semanticRange; } var result = razorRanges.ToArray(); return(result); }
private void AddSemanticRange(SyntaxNode node, int semanticKind) { var source = _razorCodeDocument.Source; var range = node.GetRange(source); var semanticRange = new SemanticRange(semanticKind, range, modifier: 0); if (_range is null || semanticRange.Range.OverlapsWith(_range)) { _semanticRanges.Add(semanticRange); } }
private void AddSemanticRange(SyntaxNode node, int semanticKind) { if (node is null) { // This can happen in situations like "<p class='", where the trailing ' hasn't been typed yet. return; } if (node.Width == 0) { // Under no circumstances can we have 0-width spans. // This can happen in situations like "@* comment ", where EndCommentStar and EndCommentTransition are empty. return; } var source = _razorCodeDocument.Source; var range = node.GetRange(source); // LSP spec forbids multi-line tokens, so we need to split this up. // Thankfully all instances of this have multiple component tokens. if (range.Start.Line != range.End.Line) { // We have to iterate over the individual nodes because this node might consist of multiple lines // ie: "/r/ntext/r/n" would be parsed as one node containing three elements (newline, "text", newline). foreach (var token in node.ChildNodes()) { // We skip whitespace to avoid "multiline" ranges for "/r/n", where the /n is interpreted as being on a new line. // This also stops us from returning data for " ", which seems like a nice side-effect as it's not likly to have any colorization anyway. if (!token.ContainsOnlyWhitespace()) { var tokenRange = token.GetRange(source); var semantic = new SemanticRange(semanticKind, tokenRange, modifier: 0); AddRange(semantic); } } } else { var semanticRange = new SemanticRange(semanticKind, range, modifier: 0); AddRange(semanticRange); } void AddRange(SemanticRange semanticRange) { if (_range is null || semanticRange.Range.OverlapsWith(_range)) { _semanticRanges.Add(semanticRange); } } }
private SemanticRange CreateSemanticRange(SyntaxNode node, SyntaxKind kind) { uint kindUint; switch (kind) { case SyntaxKind.MarkupTagHelperDirectiveAttribute: case SyntaxKind.MarkupMinimizedTagHelperDirectiveAttribute: kindUint = SemanticTokensLegend.TokenTypesLegend[SemanticTokensLegend.RazorDirectiveAttribute]; break; case SyntaxKind.MarkupTagHelperStartTag: case SyntaxKind.MarkupTagHelperEndTag: kindUint = SemanticTokensLegend.TokenTypesLegend[SemanticTokensLegend.RazorTagHelperElement]; break; case SyntaxKind.MarkupTagHelperAttribute: case SyntaxKind.MarkupMinimizedTagHelperAttribute: kindUint = SemanticTokensLegend.TokenTypesLegend[SemanticTokensLegend.RazorTagHelperAttribute]; break; case SyntaxKind.Transition: kindUint = SemanticTokensLegend.TokenTypesLegend[SemanticTokensLegend.RazorTransition]; break; case SyntaxKind.Colon: kindUint = SemanticTokensLegend.TokenTypesLegend[SemanticTokensLegend.RazorDirectiveColon]; break; default: throw new NotImplementedException(); } var source = _razorCodeDocument.Source; var range = node.GetRange(source); var result = new SemanticRange(kindUint, range); return(result); }
/** * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: * - at index `5*i` - `deltaLine`: token line number, relative to the previous token * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes` * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` **/ private static IEnumerable <uint> GetData( SemanticRange currentRange, SemanticRange previousRange, RazorCodeDocument razorCodeDocument) { // var previousRange = previousRange?.Range; // var currentRange = currentRange.Range; // deltaLine var previousLineIndex = previousRange?.Range == null ? 0 : previousRange.Range.Start.Line; yield return((uint)(currentRange.Range.Start.Line - previousLineIndex)); // deltaStart if (previousRange != null && previousRange?.Range.Start.Line == currentRange.Range.Start.Line) { yield return((uint)(currentRange.Range.Start.Character - previousRange.Range.Start.Character)); } else { yield return((uint)currentRange.Range.Start.Character); } // length var textSpan = currentRange.Range.AsTextSpan(razorCodeDocument.GetSourceText()); var length = textSpan.Length; Debug.Assert(length > 0); yield return((uint)length); // tokenType yield return(currentRange.Kind); // tokenModifiers // We don't currently have any need for tokenModifiers yield return(0); }
private void AddSemanticRange(SyntaxNode node, int semanticKind) { if (node is null) { throw new ArgumentNullException(nameof(node)); } if (node.Width == 0) { // Under no circumstances can we have 0-width spans. // This can happen in situations like "@* comment ", where EndCommentStar and EndCommentTransition are empty. return; } var source = _razorCodeDocument.Source; var range = node.GetRange(source); var semanticRange = new SemanticRange(semanticKind, range, modifier: 0); if (_range is null || semanticRange.Range.OverlapsWith(_range)) { _semanticRanges.Add(semanticRange); } }
private void AddSemanticRange(SyntaxNode node, int semanticKind) { if (node is null) { // This can happen in situations like "<p class='", where the trailing ' hasn't been typed yet. return; } if (node.Width == 0) { // Under no circumstances can we have 0-width spans. // This can happen in situations like "@* comment ", where EndCommentStar and EndCommentTransition are empty. return; } var source = _razorCodeDocument.Source; var range = node.GetRange(source); // LSP spec forbids multi-line tokens, so we need to split this up. if (range.Start.Line != range.End.Line) { var childNodes = node.ChildNodes(); if (childNodes.Count == 0) { var content = node.GetContent(); var lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); var charPosition = range.Start.Character; for (var i = 0; i < lines.Length; i++) { var startPosition = new Position(range.Start.Line + i, charPosition); var endPosition = new Position(range.Start.Line + i, charPosition + lines[i].Length); var lineRange = new Range(startPosition, endPosition); var semantic = new SemanticRange(semanticKind, lineRange, modifier: 0); AddRange(semantic); charPosition = 0; } } else { // We have to iterate over the individual nodes because this node might consist of multiple lines // ie: "/r/ntext/r/n" would be parsed as one node containing three elements (newline, "text", newline). foreach (var token in node.ChildNodes()) { // We skip whitespace to avoid "multiline" ranges for "/r/n", where the /n is interpreted as being on a new line. // This also stops us from returning data for " ", which seems like a nice side-effect as it's not likly to have any colorization anyway. if (!token.ContainsOnlyWhitespace()) { var tokenRange = token.GetRange(source); var semantic = new SemanticRange(semanticKind, tokenRange, modifier: 0); AddRange(semantic); } } } } else { var semanticRange = new SemanticRange(semanticKind, range, modifier: 0); AddRange(semanticRange); } void AddRange(SemanticRange semanticRange) { if (semanticRange.Range.Start != semanticRange.Range.End) { _semanticRanges.Add(semanticRange); } } }