public override bool TryMapToProjectedDocumentRange(RazorCodeDocument codeDocument, Range originalRange, out Range projectedRange)
        {
            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (originalRange is null)
            {
                throw new ArgumentNullException(nameof(originalRange));
            }

            projectedRange = default;

            if ((originalRange.End.Line < originalRange.Start.Line) ||
                (originalRange.End.Line == originalRange.Start.Line &&
                 originalRange.End.Character < originalRange.Start.Character))
            {
                Debug.Fail($"DefaultRazorDocumentMappingService:TryMapToProjectedDocumentRange original range end < start '{originalRange}'");
                return(false);
            }

            var sourceText = codeDocument.GetSourceText();
            var range      = originalRange;

            var startIndex = range.Start.GetAbsoluteIndex(sourceText);

            if (!TryMapToProjectedDocumentPosition(codeDocument, startIndex, out var projectedStart, out var _))
            {
                return(false);
            }

            var endIndex = range.End.GetAbsoluteIndex(sourceText);

            if (!TryMapToProjectedDocumentPosition(codeDocument, endIndex, out var projectedEnd, out var _))
            {
                return(false);
            }

            // Ensures a valid range is returned.
            // As we're doing two seperate TryMapToProjectedDocumentPosition calls,
            // it's possible the projectedStart and projectedEnd positions are in completely
            // different places in the document, including the possibility that the
            // projectedEnd position occurs before the projectedStart position.
            // We explicitly disallow such ranges where the end < start.
            if ((projectedEnd.Line < projectedStart.Line) ||
                (projectedEnd.Line == projectedStart.Line &&
                 projectedEnd.Character < projectedStart.Character))
            {
                return(false);
            }

            projectedRange = new Range(
                projectedStart,
                projectedEnd);

            return(true);
        }
        /**
         * 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(
            SyntaxResult currentNode,
            SyntaxResult?previousNode,
            RazorCodeDocument razorCodeDocument)
        {
            var previousRange = previousNode?.Range;
            var currentRange  = currentNode.Range;

            // deltaLine
            var previousLineIndex = previousNode == null ? 0 : previousRange.Start.Line;

            yield return((uint)(currentRange.Start.Line - previousLineIndex));

            // deltaStart
            if (previousRange != null && previousRange?.Start.Line == currentRange.Start.Line)
            {
                yield return((uint)(currentRange.Start.Character - previousRange.Start.Character));
            }
            else
            {
                yield return((uint)currentRange.Start.Character);
            }

            // length
            var endIndex   = currentNode.Range.End.GetAbsoluteIndex(razorCodeDocument.GetSourceText());
            var startIndex = currentNode.Range.Start.GetAbsoluteIndex(razorCodeDocument.GetSourceText());
            var length     = endIndex - startIndex;

            Debug.Assert(length > 0);
            yield return((uint)length);

            // tokenType
            yield return(GetTokenTypeData(currentNode.Kind));

            // tokenModifiers
            // We don't currently have any need for tokenModifiers
            yield return(0);
        }
Exemplo n.º 3
0
        public override RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int originalIndex)
        {
            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            var syntaxTree      = codeDocument.GetSyntaxTree();
            var classifiedSpans = syntaxTree.GetClassifiedSpans();
            var tagHelperSpans  = syntaxTree.GetTagHelperSpans();
            var documentLength  = codeDocument.GetSourceText().Length;
            var languageKind    = GetLanguageKindCore(classifiedSpans, tagHelperSpans, originalIndex, documentLength);

            return(languageKind);
        }
        public static IReadOnlyList <SemanticRange> VisitAllNodes(RazorCodeDocument razorCodeDocument, Range?range = null)
        {
            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);

            return(visitor._semanticRanges);
        }
        // Internal for testing only
        internal static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, Range razorRange, [NotNullWhen(true)] out Range?csharpRange)
        {
            SourceSpan?minGeneratedSpan = null;
            SourceSpan?maxGeneratedSpan = null;

            var sourceText = codeDocument.GetSourceText();
            var textSpan   = razorRange.AsTextSpan(sourceText);
            var csharpDoc  = codeDocument.GetCSharpDocument();

            // We want to find the min and max C# source mapping that corresponds with our Razor range.
            foreach (var mapping in csharpDoc.SourceMappings)
            {
                var mappedTextSpan = mapping.OriginalSpan.AsTextSpan();

                if (textSpan.OverlapsWith(mappedTextSpan))
                {
                    if (minGeneratedSpan is null || mapping.GeneratedSpan.AbsoluteIndex < minGeneratedSpan.Value.AbsoluteIndex)
                    {
                        minGeneratedSpan = mapping.GeneratedSpan;
                    }

                    var mappingEndIndex = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length;
                    if (maxGeneratedSpan is null || mappingEndIndex > maxGeneratedSpan.Value.AbsoluteIndex + maxGeneratedSpan.Value.Length)
                    {
                        maxGeneratedSpan = mapping.GeneratedSpan;
                    }
                }
            }

            // 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);

                csharpRange = new Range {
                    Start = startRange.Start, End = endRange.End
                };
                Debug.Assert(csharpRange.Start <= csharpRange.End, "Range.Start should not be larger than Range.End");

                return(true);
            }

            csharpRange = null;
            return(false);
        }
Exemplo n.º 6
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);
        }
        public override bool TryMapToProjectedDocumentRange(RazorCodeDocument codeDocument, Range originalRange, out Range projectedRange)
        {
            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (originalRange is null)
            {
                throw new ArgumentNullException(nameof(originalRange));
            }

            projectedRange = default;
            var sourceText = codeDocument.GetSourceText();
            var range      = originalRange;

            var startIndex = range.Start.GetAbsoluteIndex(sourceText);

            if (!TryMapToProjectedDocumentPosition(codeDocument, startIndex, out var projectedStart, out var _))
            {
                return(false);
            }

            var endIndex = range.End.GetAbsoluteIndex(sourceText);

            if (!TryMapToProjectedDocumentPosition(codeDocument, endIndex, out var projectedEnd, out var _))
            {
                return(false);
            }

            projectedRange = new Range(
                projectedStart,
                projectedEnd);

            return(true);
        }
Exemplo n.º 8
0
        public static FormattingContext Create(
            DocumentUri uri,
            DocumentSnapshot originalSnapshot,
            RazorCodeDocument codeDocument,
            FormattingOptions options,
            AdhocWorkspaceFactory workspaceFactory,
            bool isFormatOnType = false)
        {
            if (uri is null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (originalSnapshot is null)
            {
                throw new ArgumentNullException(nameof(originalSnapshot));
            }

            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (workspaceFactory is null)
            {
                throw new ArgumentNullException(nameof(workspaceFactory));
            }

            var syntaxTree      = codeDocument.GetSyntaxTree();
            var formattingSpans = syntaxTree.GetFormattingSpans();

            var result = new FormattingContext(workspaceFactory)
            {
                Uri = uri,
                OriginalSnapshot = originalSnapshot,
                CodeDocument     = codeDocument,
                Options          = options,
                IsFormatOnType   = isFormatOnType,
                FormattingSpans  = formattingSpans
            };

            var sourceText   = codeDocument.GetSourceText();
            var indentations = new Dictionary <int, IndentationContext>();

            var previousIndentationLevel = 0;

            for (var i = 0; i < sourceText.Lines.Count; i++)
            {
                // Get first non-whitespace character position
                var nonWsPos            = sourceText.Lines[i].GetFirstNonWhitespacePosition();
                var existingIndentation = (nonWsPos ?? sourceText.Lines[i].End) - sourceText.Lines[i].Start;

                // The existingIndentation above is measured in characters, and is used to create text edits
                // The below is measured in columns, so takes into account tab size. This is useful for creating
                // new indentation strings
                var existingIndentationSize = sourceText.Lines[i].GetIndentationSize(options.TabSize);

                var emptyOrWhitespaceLine = false;
                if (nonWsPos == null)
                {
                    emptyOrWhitespaceLine = true;
                    nonWsPos = sourceText.Lines[i].Start;
                }

                // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan.
                if (result.TryGetFormattingSpan(nonWsPos.Value, out var span))
                {
                    indentations[i] = new IndentationContext(firstSpan: span)
                    {
                        Line = i,
                        RazorIndentationLevel    = span.RazorIndentationLevel,
                        HtmlIndentationLevel     = span.HtmlIndentationLevel,
                        RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel,
                        ExistingIndentation      = existingIndentation,
                        ExistingIndentationSize  = existingIndentationSize,
                        EmptyOrWhitespaceLine    = emptyOrWhitespaceLine,
                    };
                    previousIndentationLevel = span.IndentationLevel;
                }
                else
                {
                    // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line.
                    // Let's create a 0 length span to represent this and default it to HTML.
                    var placeholderSpan = new FormattingSpan(
                        new Language.Syntax.TextSpan(nonWsPos.Value, 0),
                        new Language.Syntax.TextSpan(nonWsPos.Value, 0),
                        FormattingSpanKind.Markup,
                        FormattingBlockKind.Markup,
                        razorIndentationLevel: 0,
                        htmlIndentationLevel: 0,
                        isInClassBody: false,
                        componentLambdaNestingLevel: 0);

                    indentations[i] = new IndentationContext(firstSpan: placeholderSpan)
                    {
                        Line = i,
                        RazorIndentationLevel    = 0,
                        HtmlIndentationLevel     = 0,
                        RelativeIndentationLevel = previousIndentationLevel,
                        ExistingIndentation      = existingIndentation,
                        ExistingIndentationSize  = existingIndentation,
                        EmptyOrWhitespaceLine    = emptyOrWhitespaceLine,
                    };
                }
            }

            result.Indentations = indentations;

            return(result);
        }
        public static FormattingContext Create(
            DocumentUri uri,
            DocumentSnapshot originalSnapshot,
            RazorCodeDocument codeDocument,
            FormattingOptions options,
            Range range         = null,
            bool isFormatOnType = false)
        {
            if (uri is null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (originalSnapshot is null)
            {
                throw new ArgumentNullException(nameof(originalSnapshot));
            }

            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            var text = codeDocument.GetSourceText();

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);

            var result = new FormattingContext()
            {
                Uri = uri,
                OriginalSnapshot = originalSnapshot,
                CodeDocument     = codeDocument,
                Range            = range,
                Options          = options,
                IsFormatOnType   = isFormatOnType
            };

            var sourceText      = codeDocument.GetSourceText();
            var syntaxTree      = codeDocument.GetSyntaxTree();
            var formattingSpans = syntaxTree.GetFormattingSpans();
            var indentations    = new Dictionary <int, IndentationContext>();

            var previousIndentationLevel = 0;

            for (var i = 0; i < sourceText.Lines.Count; i++)
            {
                // Get first non-whitespace character position
                var nonWsPos              = sourceText.Lines[i].GetFirstNonWhitespacePosition();
                var existingIndentation   = (nonWsPos ?? sourceText.Lines[i].End) - sourceText.Lines[i].Start;
                var emptyOrWhitespaceLine = false;
                if (nonWsPos == null)
                {
                    emptyOrWhitespaceLine = true;
                    nonWsPos = sourceText.Lines[i].Start;
                }

                // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan.
                if (TryGetFormattingSpan(nonWsPos.Value, formattingSpans, out var span))
                {
                    indentations[i] = new IndentationContext
                    {
                        Line = i,
                        RazorIndentationLevel    = span.RazorIndentationLevel,
                        HtmlIndentationLevel     = span.HtmlIndentationLevel,
                        RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel,
                        ExistingIndentation      = existingIndentation,
                        FirstSpan             = span,
                        EmptyOrWhitespaceLine = emptyOrWhitespaceLine,
                    };
                    previousIndentationLevel = span.IndentationLevel;
                }
                else
                {
                    // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line.
                    // Let's create a 0 length span to represent this and default it to HTML.
                    var placeholderSpan = new FormattingSpan(
                        new Language.Syntax.TextSpan(nonWsPos.Value, 0),
                        new Language.Syntax.TextSpan(nonWsPos.Value, 0),
                        FormattingSpanKind.Markup,
                        FormattingBlockKind.Markup,
                        razorIndentationLevel: 0,
                        htmlIndentationLevel: 0,
                        isInClassBody: false);

                    indentations[i] = new IndentationContext
                    {
                        Line = i,
                        RazorIndentationLevel    = 0,
                        HtmlIndentationLevel     = 0,
                        RelativeIndentationLevel = previousIndentationLevel,
                        ExistingIndentation      = existingIndentation,
                        FirstSpan             = placeholderSpan,
                        EmptyOrWhitespaceLine = emptyOrWhitespaceLine,
                    };
                }
            }

            result.Indentations = indentations;

            return(result);
        }
        public static FormattingContext Create(
            Uri uri,
            DocumentSnapshot originalSnapshot,
            RazorCodeDocument codedocument,
            FormattingOptions options,
            Range range         = null,
            bool isFormatOnType = false)
        {
            if (uri is null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (originalSnapshot is null)
            {
                throw new ArgumentNullException(nameof(originalSnapshot));
            }

            if (codedocument is null)
            {
                throw new ArgumentNullException(nameof(codedocument));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            var text = codedocument.GetSourceText();

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);

            var result = new FormattingContext()
            {
                Uri = uri,
                OriginalSnapshot = originalSnapshot,
                CodeDocument     = codedocument,
                Range            = range,
                Options          = options,
                IsFormatOnType   = isFormatOnType
            };

            var source          = codedocument.Source;
            var syntaxTree      = codedocument.GetSyntaxTree();
            var formattingSpans = syntaxTree.GetFormattingSpans();

            var total = 0;
            var previousIndentationLevel = 0;

            for (var i = 0; i < source.Lines.Count; i++)
            {
                // Get first non-whitespace character position
                var lineLength = source.Lines.GetLineLength(i);
                var nonWsChar  = 0;
                for (var j = 0; j < lineLength; j++)
                {
                    var ch = source[total + j];
                    if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch))
                    {
                        nonWsChar = j;
                        break;
                    }
                }

                // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan.
                if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span))
                {
                    result.Indentations[i] = new IndentationContext
                    {
                        Line                     = i,
                        IndentationLevel         = span.IndentationLevel,
                        RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel,
                        ExistingIndentation      = nonWsChar,
                        FirstSpan                = span,
                    };
                    previousIndentationLevel = span.IndentationLevel;
                }
                else
                {
                    // Couldn't find a corresponding FormattingSpan.
                    result.Indentations[i] = new IndentationContext
                    {
                        Line                     = i,
                        IndentationLevel         = -1,
                        RelativeIndentationLevel = previousIndentationLevel,
                        ExistingIndentation      = nonWsChar,
                    };
                }

                total += lineLength;
            }

            return(result);
        }
Exemplo n.º 11
0
        public static FormattingContext Create(
            DocumentUri uri,
            DocumentSnapshot originalSnapshot,
            RazorCodeDocument codeDocument,
            FormattingOptions options,
            Range range         = null,
            bool isFormatOnType = false)
        {
            if (uri is null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (originalSnapshot is null)
            {
                throw new ArgumentNullException(nameof(originalSnapshot));
            }

            if (codeDocument is null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            var text = codeDocument.GetSourceText();

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);

            var result = new FormattingContext()
            {
                Uri = uri,
                OriginalSnapshot = originalSnapshot,
                CodeDocument     = codeDocument,
                Range            = range,
                Options          = options,
                IsFormatOnType   = isFormatOnType
            };

            var source          = codeDocument.Source;
            var syntaxTree      = codeDocument.GetSyntaxTree();
            var formattingSpans = syntaxTree.GetFormattingSpans();
            var indentations    = new Dictionary <int, IndentationContext>();

            var total = 0;
            var previousIndentationLevel = 0;

            for (var i = 0; i < source.Lines.Count; i++)
            {
                // Get first non-whitespace character position
                var lineLength = source.Lines.GetLineLength(i);
                var nonWsChar  = 0;
                for (var j = 0; j < lineLength; j++)
                {
                    var ch = source[total + j];
                    if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch))
                    {
                        nonWsChar = j;
                        break;
                    }
                }

                // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan.
                if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span))
                {
                    indentations[i] = new IndentationContext
                    {
                        Line                     = i,
                        IndentationLevel         = span.IndentationLevel,
                        RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel,
                        ExistingIndentation      = nonWsChar,
                        FirstSpan                = span,
                    };
                    previousIndentationLevel = span.IndentationLevel;
                }
                else
                {
                    // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line.
                    // Let's create a 0 length span to represent this and default it to HTML.
                    var placeholderSpan = new FormattingSpan(
                        new Language.Syntax.TextSpan(total + nonWsChar, 0),
                        new Language.Syntax.TextSpan(total + nonWsChar, 0),
                        FormattingSpanKind.Markup,
                        FormattingBlockKind.Markup,
                        indentationLevel: 0,
                        isInClassBody: false);

                    indentations[i] = new IndentationContext
                    {
                        Line                     = i,
                        IndentationLevel         = 0,
                        RelativeIndentationLevel = previousIndentationLevel,
                        ExistingIndentation      = nonWsChar,
                        FirstSpan                = placeholderSpan,
                    };
                }

                total += lineLength;
            }

            result.Indentations = indentations;

            return(result);
        }