Пример #1
0
        private List <TextChange> AdjustIndentation(FormattingContext context, CancellationToken cancellationToken, Range range = null)
        {
            // In this method, the goal is to make final adjustments to the indentation of each line.
            // We will take into account the following,
            // 1. The indentation due to nested C# structures
            // 2. The indentation due to Razor and HTML constructs

            var text = context.SourceText;

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

            // First, let's build an understanding of the desired C# indentation at the beginning and end of each source mapping.
            var sourceMappingIndentations = new SortedDictionary <int, int>();

            foreach (var mapping in context.CodeDocument.GetCSharpDocument().SourceMappings)
            {
                var mappingSpan  = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                var mappingRange = mappingSpan.AsRange(context.SourceText);
                if (!ShouldFormat(context, mappingRange.Start))
                {
                    // We don't care about this range as this can potentially lead to incorrect scopes.
                    continue;
                }

                var startIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex, cancellationToken);
                sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex] = startIndentation;

                var endIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length + 1, cancellationToken);
                sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length + 1] = endIndentation;
            }

            var sourceMappingIndentationScopes = sourceMappingIndentations.Keys.ToArray();

            // Now, let's combine the C# desired indentation with the Razor and HTML indentation for each line.
            var newIndentations = new Dictionary <int, int>();

            for (var i = range.Start.Line; i <= range.End.Line; i++)
            {
                var line = context.SourceText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // We don't want to indent empty lines.
                    continue;
                }

                var lineStart = line.Start;
                int csharpDesiredIndentation;
                if (DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, lineStart, out _, out var projectedLineStart))
                {
                    // We were able to map this line to C# directly.
                    csharpDesiredIndentation = CSharpFormatter.GetCSharpIndentation(context, projectedLineStart, cancellationToken);
                }
                else
                {
                    // Couldn't remap. This is probably a non-C# location.
                    // Use SourceMapping indentations to locate the C# scope of this line.
                    // E.g,
                    //
                    // @if (true) {
                    //   <div>
                    //  |</div>
                    // }
                    //
                    // We can't find a direct mapping at |, but we can infer its base indentation from the
                    // indentation of the latest source mapping prior to this line.
                    // We use binary search to find that spot.

                    var index = Array.BinarySearch(sourceMappingIndentationScopes, lineStart);
                    if (index < 0)
                    {
                        // Couldn't find the exact value. Find the index of the element to the left of the searched value.
                        index = (~index) - 1;
                    }

                    // This will now be set to the same value as the end of the closest source mapping.
                    csharpDesiredIndentation = index < 0 ? 0 : sourceMappingIndentations[sourceMappingIndentationScopes[index]];
                }

                // Now let's use that information to figure out the effective C# indentation.
                // This should be based on context.
                // For instance, lines inside @code/@functions block should be reduced one level
                // and lines inside @{} should be reduced by two levels.

                var csharpDesiredIndentLevel = context.GetIndentationLevelForOffset(csharpDesiredIndentation);
                var minCSharpIndentLevel     = context.Indentations[i].MinCSharpIndentLevel;
                if (csharpDesiredIndentLevel < minCSharpIndentLevel)
                {
                    // CSharp formatter doesn't want to indent this. Let's not touch it.
                    continue;
                }

                var effectiveCSharpDesiredIndentationLevel = csharpDesiredIndentLevel - minCSharpIndentLevel;
                var razorDesiredIndentationLevel           = context.Indentations[i].IndentationLevel;
                if (!context.Indentations[i].StartsInCSharpContext)
                {
                    // This is a non-C# line. Given that the HTML formatter ran before this, we can assume
                    // HTML is already correctly formatted. So we can use the existing indentation as is.
                    razorDesiredIndentationLevel = context.GetIndentationLevelForOffset(context.Indentations[i].ExistingIndentation);
                }
                var effectiveDesiredIndentationLevel = razorDesiredIndentationLevel + effectiveCSharpDesiredIndentationLevel;

                // This will now contain the indentation we ultimately want to apply to this line.
                newIndentations[i] = effectiveDesiredIndentationLevel;
            }

            // Now that we have collected all the indentations for each line, let's convert them to text edits.
            var changes = new List <TextChange>();

            foreach (var item in newIndentations)
            {
                var line             = item.Key;
                var indentationLevel = item.Value;
                Debug.Assert(indentationLevel >= 0, "Negative indent level. This is unexpected.");

                var existingIndentationLength   = context.Indentations[line].ExistingIndentation;
                var spanToReplace               = new TextSpan(context.SourceText.Lines[line].Start, existingIndentationLength);
                var effectiveDesiredIndentation = context.GetIndentationLevelString(indentationLevel);
                changes.Add(new TextChange(spanToReplace, effectiveDesiredIndentation));
            }

            return(changes);
        }