Example #1
0
        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);
        }
Example #2
0
 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);
        }
Example #5
0
        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);
            }
        }
Example #6
0
        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);
                }
            }
        }
Example #7
0
        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);
        }
Example #8
0
        /**
         * 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);
        }
Example #9
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);
                }
            }
        }