Ejemplo n.º 1
0
 public abstract Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken);
Ejemplo n.º 2
0
        protected static bool ShouldFormat(FormattingContext context, TextSpan mappingSpan, bool allowImplicitStatements)
        {
            // We should be called with the range of various C# SourceMappings.

            if (mappingSpan.Start == 0)
            {
                // The mapping starts at 0. It can't be anything special but pure C#. Let's format it.
                return(true);
            }

            var sourceText    = context.SourceText;
            var absoluteIndex = mappingSpan.Start;

            if (mappingSpan.Length > 0)
            {
                // Slightly ugly hack to get around the behavior of LocateOwner.
                // In some cases, using the start of a mapping doesn't work well
                // because LocateOwner returns the previous node due to it owning the edge.
                // So, if we can try to find the owner using a position that fully belongs to the current mapping.
                absoluteIndex = mappingSpan.Start + 1;
            }

            var change     = new SourceChange(absoluteIndex, 0, string.Empty);
            var syntaxTree = context.CodeDocument.GetSyntaxTree();
            var owner      = syntaxTree.Root.LocateOwner(change);

            if (owner == null)
            {
                // Can't determine owner of this position. Optimistically allow formatting.
                return(true);
            }

            // special case: If we're formatting implicit statements, we want to treat the `@attribute` directive as one
            // so that the C# definition of the attribute is formatted as C#
            if (allowImplicitStatements &&
                IsAttributeDirective())
            {
                return(true);
            }

            if (IsInHtmlTag() ||
                IsInDirectiveWithNoKind() ||
                IsInSingleLineDirective() ||
                IsImplicitOrExplicitExpression() ||
                IsInSectionDirective() ||
                (!allowImplicitStatements && IsImplicitStatementStart()))
            {
                return(false);
            }

            return(true);

            bool IsImplicitStatementStart()
            {
                // We will return true if the position points to the start of the C# portion of an implicit statement.
                // `@|for(...)` - true
                // `@|if(...)` - true
                // `@{|...` - false
                // `@code {|...` - false
                //

                if (owner.SpanStart == mappingSpan.Start &&
                    owner is CSharpStatementLiteralSyntax &&
                    owner.Parent is CSharpCodeBlockSyntax &&
                    owner.PreviousSpan() is CSharpTransitionSyntax)
                {
                    return(true);
                }

                // Not an implicit statement.
                return(false);
            }

            bool IsInHtmlTag()
            {
                // E.g, (| is position)
                //
                // `<p csharpattr="|Variable">` - true
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is MarkupStartTagSyntax || n is MarkupTagHelperStartTagSyntax || n is MarkupEndTagSyntax || n is MarkupTagHelperEndTagSyntax));
            }

            bool IsInDirectiveWithNoKind()
            {
                // E.g, (| is position)
                //
                // `@using |System;
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is RazorDirectiveSyntax directive && directive.DirectiveDescriptor == null));
            }

            bool IsAttributeDirective()
            {
                // E.g, (| is position)
                //
                // `@attribute |[System.Obsolete]
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is RazorDirectiveSyntax directive &&
                           directive.DirectiveDescriptor != null &&
                           directive.DirectiveDescriptor.Kind == DirectiveKind.SingleLine &&
                           directive.DirectiveDescriptor.Directive.Equals(AttributeDirective.Directive.Directive, StringComparison.Ordinal)));
            }

            bool IsInSingleLineDirective()
            {
                // E.g, (| is position)
                //
                // `@inject |SomeType SomeName` - true
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is RazorDirectiveSyntax directive && directive.DirectiveDescriptor.Kind == DirectiveKind.SingleLine));
            }

            bool IsImplicitOrExplicitExpression()
            {
                // E.g, (| is position)
                //
                // `@|foo` - true
                // `@(|foo)` - true
                //
                return(owner.AncestorsAndSelf().Any(n => n is CSharpImplicitExpressionSyntax || n is CSharpExplicitExpressionSyntax));
            }

            bool IsInSectionDirective()
            {
                var directive = owner.FirstAncestorOrSelf <RazorDirectiveSyntax>();

                if (directive != null &&
                    directive.DirectiveDescriptor.Directive == SectionDirective.Directive.Directive)
                {
                    return(true);
                }

                return(false);
            }
        }
        private void CleanupSourceMappingStart(FormattingContext context, Range sourceMappingRange, List <TextChange> changes)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // bring the first line to its own line and adjust its indentation,
            //
            // E.g,
            //
            // @{   public int x = 0;
            // }
            //
            // becomes,
            //
            // @{
            //    public int x  = 0;
            // }
            //

            if (!ShouldFormat(context, sourceMappingRange.Start, allowImplicitStatements: false))
            {
                // We don't want to run cleanup on this range.
                return;
            }

            // @{
            //     if (true)
            //     {
            //         <div></div>|
            //
            //              |}
            // }
            // We want to return the length of the range marked by |...|
            //
            var text = context.SourceText;
            var sourceMappingSpan = sourceMappingRange.AsTextSpan(text);
            var whitespaceLength  = text.GetFirstNonWhitespaceOffset(sourceMappingSpan);

            if (whitespaceLength == null)
            {
                // There was no content here. Skip.
                return;
            }

            var spanToReplace = new TextSpan(sourceMappingSpan.Start, whitespaceLength.Value);

            if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
            {
                // Can't find the correct indentation for this content. Leave it alone.
                return;
            }

            // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
            var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel);

            // After the below change the above example should look like,
            // @{
            //     if (true)
            //     {
            //         <div></div>
            //     }
            // }
            var change = new TextChange(spanToReplace, replacement);

            changes.Add(change);
        }
        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);
        }
Ejemplo n.º 5
0
        protected async Task <List <TextChange> > AdjustIndentationAsync(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);

            // To help with figuring out the correct indentation, first we will need the indentation
            // that the C# formatter wants to apply in the following locations,
            // 1. The start and end of each of our source mappings
            // 2. The start of every line that starts in C# context

            // Due to perf concerns, we only want to invoke the real C# formatter once.
            // So, let's collect all the significant locations that we want to obtain the CSharpDesiredIndentations for.

            var significantLocations = new HashSet <int>();

            // First, collect all the locations at the beginning and end of each source mapping.
            var sourceMappingMap = new Dictionary <int, int>();

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

                var originalStartLocation  = mapping.OriginalSpan.AbsoluteIndex;
                var projectedStartLocation = mapping.GeneratedSpan.AbsoluteIndex;
                sourceMappingMap[originalStartLocation] = projectedStartLocation;
                significantLocations.Add(projectedStartLocation);

                var originalEndLocation  = mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length + 1;
                var projectedEndLocation = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length + 1;
                sourceMappingMap[originalEndLocation] = projectedEndLocation;
                significantLocations.Add(projectedEndLocation);
            }

            // Next, collect all the line starts that start in C# context
            var lineStartMap = new Dictionary <int, int>();

            for (var i = range.Start.Line; i <= range.End.Line; i++)
            {
                if (context.Indentations[i].EmptyOrWhitespaceLine)
                {
                    // We should remove whitespace on empty lines.
                    continue;
                }

                var line      = context.SourceText.Lines[i];
                var lineStart = line.GetFirstNonWhitespacePosition() ?? line.Start;

                var lineStartSpan = new TextSpan(lineStart, 0);
                if (!ShouldFormat(context, lineStartSpan, allowImplicitStatements: true))
                {
                    // We don't care about this range as this can potentially lead to incorrect scopes.
                    continue;
                }

                if (DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, lineStart, out _, out var projectedLineStart))
                {
                    lineStartMap[lineStart] = projectedLineStart;
                    significantLocations.Add(projectedLineStart);
                }
            }

            // Now, invoke the C# formatter to obtain the CSharpDesiredIndentation for all significant locations.
            var significantLocationIndentation = await CSharpFormatter.GetCSharpIndentationAsync(context, significantLocations, cancellationToken);

            // Build source mapping indentation scopes.
            var sourceMappingIndentations = new SortedDictionary <int, int>();

            foreach (var originalLocation in sourceMappingMap.Keys)
            {
                var significantLocation = sourceMappingMap[originalLocation];
                if (!significantLocationIndentation.TryGetValue(significantLocation, out var indentation))
                {
                    // C# formatter didn't return an indentation for this. Skip.
                    continue;
                }

                sourceMappingIndentations[originalLocation] = indentation;
            }

            var sourceMappingIndentationScopes = sourceMappingIndentations.Keys.ToArray();

            // Build lineStart indentation map.
            var lineStartIndentations = new Dictionary <int, int>();

            foreach (var originalLocation in lineStartMap.Keys)
            {
                var significantLocation = lineStartMap[originalLocation];
                if (!significantLocationIndentation.TryGetValue(significantLocation, out var indentation))
                {
                    // C# formatter didn't return an indentation for this. Skip.
                    continue;
                }

                lineStartIndentations[originalLocation] = indentation;
            }

            // 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++)
            {
                if (context.Indentations[i].EmptyOrWhitespaceLine)
                {
                    // We should remove whitespace on empty lines.
                    newIndentations[i] = 0;
                    continue;
                }

                var minCSharpIndentation = context.GetIndentationOffsetForLevel(context.Indentations[i].MinCSharpIndentLevel);
                var line          = context.SourceText.Lines[i];
                var lineStart     = line.GetFirstNonWhitespacePosition() ?? line.Start;
                var lineStartSpan = new TextSpan(lineStart, 0);
                if (!ShouldFormat(context, lineStartSpan, allowImplicitStatements: true))
                {
                    // We don't care about this line as it lies in an area we don't want to format.
                    continue;
                }

                if (!lineStartIndentations.TryGetValue(lineStart, out var csharpDesiredIndentation))
                {
                    // 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.
                    if (index < 0)
                    {
                        csharpDesiredIndentation = 0;
                    }
                    else
                    {
                        var absoluteIndex = sourceMappingIndentationScopes[index];
                        csharpDesiredIndentation = sourceMappingIndentations[absoluteIndex];

                        // This means we didn't find an exact match and so we used the indentation of the end of a previous mapping.
                        // So let's use the MinCSharpIndentation of that same location if possible.
                        if (context.TryGetFormattingSpan(absoluteIndex, out var span))
                        {
                            minCSharpIndentation = context.GetIndentationOffsetForLevel(span.MinCSharpIndentLevel);
                        }
                    }
                }

                // 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.

                if (csharpDesiredIndentation < minCSharpIndentation)
                {
                    // CSharp formatter doesn't want to indent this. Let's not touch it.
                    continue;
                }

                var effectiveCSharpDesiredIndentation = csharpDesiredIndentation - minCSharpIndentation;
                var razorDesiredIndentation           = context.GetIndentationOffsetForLevel(context.Indentations[i].IndentationLevel);
                if (context.Indentations[i].StartsInHtmlContext)
                {
                    // This is a non-C# line.
                    if (context.IsFormatOnType)
                    {
                        // HTML formatter doesn't run in the case of format on type.
                        // Let's stick with our syntax understanding of HTML to figure out the desired indentation.
                    }
                    else
                    {
                        // 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.
                        // We need to make sure to use the indentation size, as this will get passed to
                        // GetIndentationString eventually.
                        razorDesiredIndentation = context.Indentations[i].ExistingIndentationSize;
                    }
                }

                var effectiveDesiredIndentation = razorDesiredIndentation + effectiveCSharpDesiredIndentation;

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

            // 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 indentation = item.Value;
                Debug.Assert(indentation >= 0, "Negative indentation. This is unexpected.");

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

            return(changes);
        }
Ejemplo n.º 6
0
        private SourceText CleanupDocument(FormattingContext context, Range range = null)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // adjust the indentation of the first line,
            //
            // E.g,
            //
            // @{   public int x = 0;
            // }
            //
            // becomes,
            //
            // @{
            //    public int x  = 0;
            // }
            //
            var text = context.SourceText;

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);
            var csharpDocument = context.CodeDocument.GetCSharpDocument();

            var changes = new List <TextChange>();

            foreach (var mapping in csharpDocument.SourceMappings)
            {
                var mappingSpan  = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                var mappingRange = mappingSpan.AsRange(text);
                if (!range.LineOverlapsWith(mappingRange))
                {
                    // We don't care about this range. It didn't change.
                    continue;
                }

                var mappingStartLineIndex = (int)mappingRange.Start.Line;
                if (context.Indentations[mappingStartLineIndex].StartsInCSharpContext)
                {
                    // Doesn't need cleaning up.
                    // For corner cases like (Range marked with |...|),
                    // @{
                    //     if (true} { <div></div>| }|
                    // }
                    // We want to leave it alone because tackling it here is really complicated.
                    continue;
                }

                // @{
                //     if (true)
                //     {
                //         <div></div>|
                //
                //              |}
                // }
                // We want to return the length of the range marked by |...|
                //
                var whitespaceLength = text.GetFirstNonWhitespaceOffset(mappingSpan);
                if (whitespaceLength == null)
                {
                    // There was no content here. Skip.
                    continue;
                }

                var spanToReplace = new TextSpan(mappingSpan.Start, whitespaceLength.Value);
                if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
                {
                    // Can't find the correct indentation for this content. Leave it alone.
                    continue;
                }

                // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
                var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel);

                // After the below change the above example should look like,
                // @{
                //     if (true)
                //     {
                //         <div></div>
                //     }
                // }
                var change = new TextChange(spanToReplace, replacement);
                changes.Add(change);
            }

            var changedText = text.WithChanges(changes);

            return(changedText);
        }
 public virtual FormattingResult Execute(FormattingContext context, FormattingResult result)
 {
     return(result);
 }
        private async Task <TextEdit[]> FormatCodeBlockDirectivesAsync(FormattingContext context)
        {
            // A code block directive is any extensible directive that can contain C# code. Here is how we represent it,
            // E.g,
            //
            //     @code {  public class Foo { }  }
            // ^                                  ^ ----> Full code block directive range (Includes preceding whitespace)
            //     ^                              ^ ----> Directive range
            //      ^                             ^ ----> DirectiveBody range
            //            ^                      ^  ----> inner codeblock range
            //
            // In this method, we are going to do the following for each code block directive,
            // 1. Format the inner codeblock using the C# formatter
            // 2. Adjust the absolute indentation of the lines formatted by the C# formatter while maintaining the relative indentation
            // 3. Indent the start of the code block (@code {) correctly and move any succeeding code to a separate line
            // 4. Indent the end of the code block (}) correctly and move it to a separate line if necessary
            // 5. Once all the edits are applied, compute the diff for this particular code block and add it to the global list of edits
            //
            var source     = context.CodeDocument.Source;
            var syntaxTree = context.CodeDocument.GetSyntaxTree();
            var nodes      = syntaxTree.GetCodeBlockDirectives();

            var allEdits = new List <TextEdit>();

            // Iterate in reverse so that the newline changes don't affect the next code block directive.
            for (var i = nodes.Length - 1; i >= 0; i--)
            {
                var directive = nodes[i];
                if (!(directive.Body is RazorDirectiveBodySyntax directiveBody))
                {
                    // This can't happen realistically. Just being defensive.
                    continue;
                }

                // Get the inner code block node that contains the actual code.
                var innerCodeBlockNode = directiveBody.CSharpCode.DescendantNodes().FirstOrDefault(n => n is CSharpCodeBlockSyntax);
                if (innerCodeBlockNode == null)
                {
                    // Nothing to indent.
                    continue;
                }

                if (innerCodeBlockNode.DescendantNodes().Any(n =>
                                                             n is MarkupBlockSyntax ||
                                                             n is CSharpTransitionSyntax ||
                                                             n is RazorCommentBlockSyntax))
                {
                    // We currently don't support formatting code block directives with Markup or other Razor constructs.
                    continue;
                }

                var originalText        = context.SourceText;
                var changedText         = originalText;
                var innerCodeBlockRange = innerCodeBlockNode.GetRange(source);

                // Compute the range inside the code block that overlaps with the provided input range.
                var rangeToFormat = innerCodeBlockRange.Overlap(context.Range);
                if (rangeToFormat != null)
                {
                    var codeEdits = await _csharpFormatter.FormatAsync(context.CodeDocument, rangeToFormat, context.Uri, context.Options);

                    changedText = ApplyCSharpEdits(context, innerCodeBlockRange, codeEdits, minCSharpIndentLevel: 2);
                }

                var edits = new List <TextEdit>();
                FormatCodeBlockStart(context, changedText, directiveBody, innerCodeBlockNode, edits);
                FormatCodeBlockEnd(context, changedText, directiveBody, innerCodeBlockNode, edits);
                changedText = ApplyChanges(changedText, edits.Select(e => e.AsTextChange(changedText)));

                // We've now applied all the edits we wanted to do. We now need to identify everything that changed in the given code block.
                // We need to include the preceding newline in our input range because we could have unindented the code block to achieve the correct indentation.
                // Without including the preceding newline, that edit would be lost.
                var fullCodeBlockDirectiveSpan = GetSpanIncludingPrecedingWhitespaceInLine(originalText, directive.Position, directive.EndPosition);
                var changes = Diff(originalText, changedText, fullCodeBlockDirectiveSpan);

                var transformedEdits = changes.Select(c => c.AsTextEdit(originalText));
                allEdits.AddRange(transformedEdits);
            }

            return(allEdits.ToArray());
        }
        //
        // 'minCSharpIndentLevel' refers to the minimum level of how much the C# formatter would indent code.
        // @code/@functions blocks contain class members and so are typically indented by 2 levels.
        // @{} blocks are put inside method body which means they are typically indented by 3 levels.
        //
        private SourceText ApplyCSharpEdits(FormattingContext context, Range codeBlockRange, TextEdit[] edits, int minCSharpIndentLevel)
        {
            var originalText          = context.SourceText;
            var originalCodeBlockSpan = codeBlockRange.AsTextSpan(originalText);

            // Sometimes the C# formatter edits outside the range we supply. Filter out those edits.
            var changes = edits.Select(e => e.AsTextChange(originalText)).Where(c => originalCodeBlockSpan.Contains(c.Span)).ToArray();

            if (changes.Length == 0)
            {
                return(originalText);
            }

            // Apply the C# edits to the document.
            var changedText = originalText.WithChanges(changes);

            TrackChangeInSpan(originalText, originalCodeBlockSpan, changedText, out var changedCodeBlockSpan, out var changeEncompassingSpan);

            // We now have the changed document with C# edits. But it might be indented more/less than what we want depending on the context.
            // So, we want to bring each line to the right level of indentation based on where the block is in the document.
            // We also need to only do this for the lines that are part of the input range to respect range formatting.
            var desiredIndentationLevel = context.Indentations[(int)codeBlockRange.Start.Line].IndentationLevel + 1;
            var editsToApply            = new List <TextChange>();
            var inputSpan = context.Range.AsTextSpan(originalText);

            TrackChangeInSpan(originalText, inputSpan, changedText, out var changedInputSpan, out _);
            var changedInputRange = changedInputSpan.AsRange(changedText);

            for (var i = (int)changedInputRange.Start.Line; i <= changedInputRange.End.Line; i++)
            {
                var line = changedText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // Empty line. C# formatter didn't remove it so we won't either.
                    continue;
                }

                if (!changedCodeBlockSpan.Contains(line.Start))
                {
                    // Defensive check to make sure we're not handling lines that are not part of the current code block.
                    continue;
                }

                var leadingWhitespace     = line.GetLeadingWhitespace();
                var minCSharpIndentLength = GetIndentationString(context, minCSharpIndentLevel).Length;
                if (leadingWhitespace.Length < minCSharpIndentLength)
                {
                    // For whatever reason, the C# formatter decided to not indent this. Leave it as is.
                    continue;
                }
                else
                {
                    // At this point we assume the C# formatter has relatively indented this line to the correct level.
                    // All we want to do at this point is to indent/unindent this line based on the absolute indentation of the block
                    // and the minimum C# indent level. We don't need to worry about the actual existing indentation here because it doesn't matter.
                    var effectiveDesiredIndentationLevel = desiredIndentationLevel - minCSharpIndentLevel;
                    var effectiveDesiredIndentation      = GetIndentationString(context, Math.Abs(effectiveDesiredIndentationLevel));
                    if (effectiveDesiredIndentationLevel < 0)
                    {
                        // This means that we need to unindent.
                        var span = new TextSpan(line.Start, effectiveDesiredIndentation.Length);
                        editsToApply.Add(new TextChange(span, string.Empty));
                    }
                    else if (effectiveDesiredIndentationLevel > 0)
                    {
                        // This means that we need to indent.
                        var span = new TextSpan(line.Start, 0);
                        editsToApply.Add(new TextChange(span, effectiveDesiredIndentation));
                    }
                }
            }

            changedText = ApplyChanges(changedText, editsToApply);
            return(changedText);
        }
Ejemplo n.º 10
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);
        }
Ejemplo n.º 11
0
        private static bool ShouldFormat(FormattingContext context, Position position)
        {
            // We should be called with start positions of various C# SourceMappings.
            if (position.Character == 0)
            {
                // The mapping starts at 0. It can't be anything special but pure C#. Let's format it.
                return(true);
            }

            var sourceText    = context.SourceText;
            var absoluteIndex = sourceText.Lines[(int)position.Line].Start + (int)position.Character;
            var syntaxTree    = context.CodeDocument.GetSyntaxTree();
            var change        = new SourceChange(absoluteIndex, 0, string.Empty);
            var owner         = syntaxTree.Root.LocateOwner(change);

            if (owner == null)
            {
                // Can't determine owner of this position. Optimistically allow formatting.
                return(true);
            }

            if (IsInHtmlTag() ||
                IsInSingleLineDirective() ||
                IsImplicitOrExplicitExpression())
            {
                return(false);
            }

            return(true);

            bool IsInHtmlTag()
            {
                // E.g, (| is position)
                //
                // `<p csharpattr="|Variable">` - true
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is MarkupStartTagSyntax || n is MarkupTagHelperStartTagSyntax || n is MarkupEndTagSyntax || n is MarkupTagHelperEndTagSyntax));
            }

            bool IsInSingleLineDirective()
            {
                // E.g, (| is position)
                //
                // `@inject |SomeType SomeName` - true
                //
                // Note: @using directives don't have a descriptor associated with them, hence the extra null check.
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is RazorDirectiveSyntax directive && (directive.DirectiveDescriptor == null || directive.DirectiveDescriptor.Kind == DirectiveKind.SingleLine)));
            }

            bool IsImplicitOrExplicitExpression()
            {
                // E.g, (| is position)
                //
                // `@|foo` - true
                // `@(|foo)` - true
                //
                return(owner.AncestorsAndSelf().Any(n => n is CSharpImplicitExpressionSyntax || n is CSharpExplicitExpressionSyntax));
            }
        }
Ejemplo n.º 12
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);
        }
Ejemplo n.º 13
0
        public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken)
        {
            if (!context.IsFormatOnType || result.Kind != RazorLanguageKind.CSharp)
            {
                // We don't want to handle regular formatting or non-C# on type formatting here.
                return(result);
            }

            // Normalize and re-map the C# edits.
            var codeDocument    = context.CodeDocument;
            var csharpText      = codeDocument.GetCSharpSourceText();
            var normalizedEdits = NormalizeTextEdits(csharpText, result.Edits);
            var mappedEdits     = RemapTextEdits(codeDocument, normalizedEdits, result.Kind);
            var filteredEdits   = FilterCSharpTextEdits(context, mappedEdits);

            if (filteredEdits.Length == 0)
            {
                // There are no CSharp edits for us to apply. No op.
                return(new FormattingResult(filteredEdits));
            }

            // Find the lines that were affected by these edits.
            var originalText = codeDocument.GetSourceText();
            var changes      = filteredEdits.Select(e => e.AsTextChange(originalText));

            // Apply the format on type edits sent over by the client.
            var formattedText  = ApplyChangesAndTrackChange(originalText, changes, out _, out var spanAfterFormatting);
            var changedContext = await context.WithTextAsync(formattedText);

            var rangeAfterFormatting = spanAfterFormatting.AsRange(formattedText);

            cancellationToken.ThrowIfCancellationRequested();

            // We make an optimistic attempt at fixing corner cases.
            var cleanupChanges = CleanupDocument(changedContext, rangeAfterFormatting);
            var cleanedText    = formattedText.WithChanges(cleanupChanges);

            changedContext = await changedContext.WithTextAsync(cleanedText);

            cancellationToken.ThrowIfCancellationRequested();

            // At this point we should have applied all edits that adds/removes newlines.
            // Let's now ensure the indentation of each of those lines is correct.

            // We only want to adjust the range that was affected.
            // We need to take into account the lines affected by formatting as well as cleanup.
            var lineDelta = LineDelta(formattedText, cleanupChanges, out var firstPosition, out var lastPosition);

            // Okay hear me out, I know this looks lazy, but it totally makes sense.
            // This method is called with edits that the C# formatter wants to make, and from those edits we work out which
            // other edits to apply etc. Fine, all good so far. BUT its totally possible that the user typed a closing brace
            // in the same position as the C# formatter thought it should be, on the line _after_ the code that the C# formatter
            // reformatted.
            //
            // For example, given:
            // if (true){
            //     }
            //
            // If the C# formatter is happy with the placement of that close brace then this method will get two edits:
            //  * On line 1 to indent the if by 4 spaces
            //  * On line 1 to add a newline and 4 spaces in front of the opening brace
            //
            // We'll happy format lines 1 and 2, and ignore the closing brace altogether. So, by looking one line further
            // we won't have that problem.
            if (rangeAfterFormatting.End.Line + lineDelta < cleanedText.Lines.Count)
            {
                lineDelta++;
            }

            // Now we know how many lines were affected by the cleanup and formatting, but we don't know where those lines are. For example, given:
            //
            // @if (true)
            // {
            //      }
            // else
            // {
            // $$}
            //
            // When typing that close brace, the changes would fix the previous close brace, but the line delta would be 0, so
            // we'd format line 6 and call it a day, even though the formatter made an edit on line 3. To fix this we use the
            // first and last position of edits made above, and make sure our range encompasses them as well. For convenience
            // we calculate these positions in the LineDelta method called above.
            // This is essentially: rangeToAdjust = new Range(Math.Min(firstFormattingEdit, userEdit), Math.Max(lastFormattingEdit, userEdit))
            var start = rangeAfterFormatting.Start;

            if (firstPosition is not null && firstPosition < start)
            {
                start = firstPosition;
            }

            var end = new Position(rangeAfterFormatting.End.Line + lineDelta, 0);

            if (lastPosition is not null && lastPosition < start)
            {
                end = lastPosition;
            }

            var rangeToAdjust = new Range(start, end);

            Debug.Assert(rangeToAdjust.End.IsValid(cleanedText), "Invalid range. This is unexpected.");

            var indentationChanges = await AdjustIndentationAsync(changedContext, cancellationToken, rangeToAdjust);

            if (indentationChanges.Count > 0)
            {
                // Apply the edits that modify indentation.
                cleanedText = cleanedText.WithChanges(indentationChanges);
            }

            // Now that we have made all the necessary changes to the document. Let's diff the original vs final version and return the diff.
            var finalChanges = cleanedText.GetTextChanges(originalText);
            var finalEdits   = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray();

            return(new FormattingResult(finalEdits));
        }
        protected bool ShouldFormat(FormattingContext context, Position position, bool allowImplicitStatements)
        {
            // We should be called with start positions of various C# SourceMappings.
            if (position.Character == 0)
            {
                // The mapping starts at 0. It can't be anything special but pure C#. Let's format it.
                return(true);
            }

            var sourceText    = context.SourceText;
            var absoluteIndex = sourceText.Lines[(int)position.Line].Start + (int)position.Character;

            if (IsImplicitStatementStart() && !allowImplicitStatements)
            {
                return(false);
            }

            var syntaxTree = context.CodeDocument.GetSyntaxTree();
            var change     = new SourceChange(absoluteIndex, 0, string.Empty);
            var owner      = syntaxTree.Root.LocateOwner(change);

            if (owner == null)
            {
                // Can't determine owner of this position. Optimistically allow formatting.
                return(true);
            }

            if (IsInHtmlTag() ||
                IsInSingleLineDirective() ||
                IsImplicitOrExplicitExpression())
            {
                return(false);
            }

            return(true);

            bool IsImplicitStatementStart()
            {
                // We will return true if the position points to the start of the C# portion of an implicit statement.
                // `@|for(...)` - true
                // `@|if(...)` - true
                // `@{|...` - false
                // `@code {|...` - false
                //

                var previousCharIndex = absoluteIndex - 1;
                var previousChar      = sourceText[previousCharIndex];

                if (previousChar != '@')
                {
                    // Not an implicit statement.
                    return(false);
                }

                // This is an implicit statement if the previous '@' is not C# (meaning it shouldn't have a projected mapping).
                return(!DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, previousCharIndex, out _, out _));
            }

            bool IsInHtmlTag()
            {
                // E.g, (| is position)
                //
                // `<p csharpattr="|Variable">` - true
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is MarkupStartTagSyntax || n is MarkupTagHelperStartTagSyntax || n is MarkupEndTagSyntax || n is MarkupTagHelperEndTagSyntax));
            }

            bool IsInSingleLineDirective()
            {
                // E.g, (| is position)
                //
                // `@inject |SomeType SomeName` - true
                //
                // Note: @using directives don't have a descriptor associated with them, hence the extra null check.
                //
                return(owner.AncestorsAndSelf().Any(
                           n => n is RazorDirectiveSyntax directive && (directive.DirectiveDescriptor == null || directive.DirectiveDescriptor.Kind == DirectiveKind.SingleLine)));
            }

            bool IsImplicitOrExplicitExpression()
            {
                // E.g, (| is position)
                //
                // `@|foo` - true
                // `@(|foo)` - true
                //
                return(owner.AncestorsAndSelf().Any(n => n is CSharpImplicitExpressionSyntax || n is CSharpExplicitExpressionSyntax));
            }
        }
 public virtual Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken)
 {
     return(Task.FromResult(Execute(context, result)));
 }
        private void FormatCodeBlockStart(FormattingContext context, SourceText changedText, RazorDirectiveBodySyntax directiveBody, SyntaxNode innerCodeBlock, List <TextEdit> edits)
        {
            var sourceText        = context.SourceText;
            var originalBodySpan  = TextSpan.FromBounds(directiveBody.Position, directiveBody.EndPosition);
            var originalBodyRange = originalBodySpan.AsRange(sourceText);

            if (context.Range.Start.Line > originalBodyRange.Start.Line)
            {
                return;
            }

            // First line is within the selected range. Let's try and format the start.

            TrackChangeInSpan(sourceText, originalBodySpan, changedText, out var changedBodySpan, out _);
            var changedBodyRange = changedBodySpan.AsRange(changedText);

            // First, make sure the first line is indented correctly.
            var firstLine = changedText.Lines[(int)changedBodyRange.Start.Line];
            var desiredIndentationLevel  = context.Indentations[firstLine.LineNumber].IndentationLevel;
            var desiredIndentation       = GetIndentationString(context, desiredIndentationLevel);
            var firstNonWhitespaceOffset = firstLine.GetFirstNonWhitespaceOffset();

            if (firstNonWhitespaceOffset.HasValue)
            {
                var edit = new TextEdit()
                {
                    Range = new Range(
                        new Position(firstLine.LineNumber, 0),
                        new Position(firstLine.LineNumber, firstNonWhitespaceOffset.Value)),
                    NewText = desiredIndentation
                };
                edits.Add(edit);
            }

            // We should also move any code that comes after '{' down to its own line.
            var originalInnerCodeBlockSpan = TextSpan.FromBounds(innerCodeBlock.Position, innerCodeBlock.EndPosition);

            TrackChangeInSpan(sourceText, originalInnerCodeBlockSpan, changedText, out var changedInnerCodeBlockSpan, out _);
            var innerCodeBlockRange = changedInnerCodeBlockSpan.AsRange(changedText);

            var innerCodeBlockLine               = changedText.Lines[(int)innerCodeBlockRange.Start.Line];
            var textAfterBlockStart              = innerCodeBlockLine.ToString().Substring(innerCodeBlock.Position - innerCodeBlockLine.Start);
            var isBlockStartOnSeparateLine       = string.IsNullOrWhiteSpace(textAfterBlockStart);
            var innerCodeBlockIndentationLevel   = desiredIndentationLevel + 1;
            var desiredInnerCodeBlockIndentation = GetIndentationString(context, innerCodeBlockIndentationLevel);
            var whitespaceAfterBlockStart        = textAfterBlockStart.GetLeadingWhitespace();

            if (!isBlockStartOnSeparateLine)
            {
                // If the first line contains code, add a newline at the beginning and indent it.
                var edit = new TextEdit()
                {
                    Range = new Range(
                        new Position(innerCodeBlockLine.LineNumber, innerCodeBlock.Position - innerCodeBlockLine.Start),
                        new Position(innerCodeBlockLine.LineNumber, innerCodeBlock.Position + whitespaceAfterBlockStart.Length - innerCodeBlockLine.Start)),
                    NewText = Environment.NewLine + desiredInnerCodeBlockIndentation
                };
                edits.Add(edit);
            }
            else
            {
                //
                // The code inside the code block directive is on its own line. Ideally the C# formatter would have already taken care of it.
                // Except, the first line of the code block is not indented because of how our SourceMappings work.
                // E.g,
                // @code {
                //     ...
                // }
                // Our source mapping for this code block only ranges between the { and }, exclusive.
                // If the C# formatter provides any edits that start from before the {, we won't be able to map it back and we will ignore it.
                // Unfortunately because of this, we partially lose some edits which would have indented the first line of the code block correctly.
                // So let's manually indent the first line here.
                //
                var innerCodeBlockText = changedText.GetSubTextString(changedInnerCodeBlockSpan);
                if (!string.IsNullOrWhiteSpace(innerCodeBlockText))
                {
                    var codeStart = innerCodeBlockText.GetFirstNonWhitespaceOffset() + changedInnerCodeBlockSpan.Start;
                    if (codeStart.HasValue && codeStart != changedInnerCodeBlockSpan.End)
                    {
                        // If we got here, it means this is a non-empty code block. We can safely indent the first line.
                        var codeStartLine = changedText.Lines.GetLineFromPosition(codeStart.Value);
                        var existingCodeStartIndentation = codeStartLine.GetFirstNonWhitespaceOffset() ?? 0;
                        var edit = new TextEdit()
                        {
                            Range = new Range(
                                new Position(codeStartLine.LineNumber, 0),
                                new Position(codeStartLine.LineNumber, existingCodeStartIndentation)),
                            NewText = desiredInnerCodeBlockIndentation
                        };
                        edits.Add(edit);
                    }
                }
            }
        }
Ejemplo n.º 17
0
        private static async Task <Dictionary <int, int> > GetCSharpIndentationCoreAsync(FormattingContext context, List <int> projectedDocumentLocations, CancellationToken cancellationToken)
        {
            // No point calling the C# formatting if we won't be interested in any of its work anyway
            if (projectedDocumentLocations.Count == 0)
            {
                return(new Dictionary <int, int>());
            }

            var(indentationMap, syntaxTree) = InitializeIndentationData(context, projectedDocumentLocations, cancellationToken);

            var root = await syntaxTree.GetRootAsync(cancellationToken);

            root = AttachAnnotations(indentationMap, projectedDocumentLocations, root);

            // At this point, we have added all the necessary markers and attached annotations.
            // Let's invoke the C# formatter and hope for the best.
            var formattedRoot = CodeAnalysis.Formatting.Formatter.Format(root, context.CSharpWorkspace, cancellationToken: cancellationToken);
            var formattedText = formattedRoot.GetText();

            var desiredIndentationMap = new Dictionary <int, int>();

            // Assuming the C# formatter did the right thing, let's extract the indentation offset from
            // the line containing trivia and token that has our attached annotations.
            ExtractTriviaAnnotations(context, formattedRoot, formattedText, desiredIndentationMap);
            ExtractTokenAnnotations(context, formattedRoot, formattedText, indentationMap, desiredIndentationMap);

            return(desiredIndentationMap);
        private static FormattingContext CreateFormattingContext(Uri uri, RazorCodeDocument codedocument, Range range, FormattingOptions options)
        {
            var result = new FormattingContext()
            {
                Uri          = uri,
                CodeDocument = codedocument,
                Range        = range,
                Options      = options
            };

            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);
        }
 public virtual Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result)
 {
     return(Task.FromResult(Execute(context, result)));
 }
Ejemplo n.º 20
0
        private static List <TextChange> AdjustRazorIndentation(FormattingContext context)
        {
            // Assume HTML formatter has already run at this point and HTML is relatively indented correctly.
            // But HTML doesn't know about Razor blocks.
            // Our goal here is to indent each line according to the surrounding Razor blocks.
            var sourceText   = context.SourceText;
            var editsToApply = new List <TextChange>();
            var indentations = context.GetIndentations();

            for (var i = 0; i < sourceText.Lines.Count; i++)
            {
                var line = sourceText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // Empty line.
                    continue;
                }

                if (indentations[i].StartsInCSharpContext)
                {
                    // Normally we don't do HTML things in C# contexts but there is one
                    // edge case when including render fragments in a C# code block, eg:
                    //
                    // @code {
                    //      void Foo()
                    //      {
                    //          Render(@<SurveyPrompt />);
                    //      {
                    // }
                    //
                    // This is popular in some libraries, like bUnit. The issue here is that
                    // the HTML formatter sees ~~~~~<SurveyPrompt /> and puts a newline before
                    // the tag, but obviously that breaks things.
                    //
                    // It's straight forward enough to just check for this situation and special case
                    // it by removing the newline again.

                    // There needs to be at least one more line, and the current line needs to end with
                    // an @ sign, and have an open angle bracket at the start of the next line.
                    if (sourceText.Lines.Count >= i + 1 &&
                        line.Text?.Length > 1 &&
                        line.Text?[line.End - 1] == '@')
                    {
                        var nextLine  = sourceText.Lines[i + 1];
                        var firstChar = nextLine.GetFirstNonWhitespaceOffset().GetValueOrDefault();

                        // When the HTML formatter inserts the newline in this scenario, it doesn't
                        // indent the component tag, so we use that as another signal that this is
                        // the scenario we think it is.
                        if (firstChar == 0 &&
                            nextLine.Text?[nextLine.Start] == '<')
                        {
                            var lineBreakLength = line.EndIncludingLineBreak - line.End;
                            var spanToReplace   = new TextSpan(line.End, lineBreakLength);
                            var change          = new TextChange(spanToReplace, string.Empty);
                            editsToApply.Add(change);

                            // Skip the next line because we've essentially just removed it.
                            i++;
                        }
                    }

                    continue;
                }

                var razorDesiredIndentationLevel = indentations[i].RazorIndentationLevel;
                if (razorDesiredIndentationLevel == 0)
                {
                    // This line isn't under any Razor specific constructs. Trust the HTML formatter.
                    continue;
                }

                var htmlDesiredIndentationLevel = indentations[i].HtmlIndentationLevel;
                if (htmlDesiredIndentationLevel == 0 && !IsPartOfHtmlTag(context, indentations[i].FirstSpan.Span.Start))
                {
                    // This line is under some Razor specific constructs but not under any HTML tag.
                    // E.g,
                    // @{
                    //          @* comment *@ <----
                    // }
                    //
                    // In this case, the HTML formatter wouldn't touch it but we should format it correctly.
                    // So, let's use our syntax understanding to rewrite the indentation.
                    // Note: This case doesn't apply for HTML tags (HTML formatter will touch it even if it is in the root).
                    // Hence the second part of the if condition.
                    //
                    var desiredIndentationLevel  = indentations[i].IndentationLevel;
                    var desiredIndentationString = context.GetIndentationLevelString(desiredIndentationLevel);
                    var spanToReplace            = new TextSpan(line.Start, indentations[i].ExistingIndentation);
                    var change = new TextChange(spanToReplace, desiredIndentationString);
                    editsToApply.Add(change);
                }
                else
                {
                    // This line is under some Razor specific constructs and HTML tags.
                    // E.g,
                    // @{
                    //    <div class="foo"
                    //         id="oof">  <----
                    //    </div>
                    // }
                    //
                    // In this case, the HTML formatter would've formatted it correctly. Let's not use our syntax understanding.
                    // Instead, we should just add to the existing indentation.
                    //
                    var razorDesiredIndentationString = context.GetIndentationLevelString(razorDesiredIndentationLevel);
                    var existingIndentationString     = context.GetIndentationString(indentations[i].ExistingIndentationSize);
                    var desiredIndentationString      = existingIndentationString + razorDesiredIndentationString;
                    var spanToReplace = new TextSpan(line.Start, indentations[i].ExistingIndentation);
                    var change        = new TextChange(spanToReplace, desiredIndentationString);
                    editsToApply.Add(change);
                }
            }

            return(editsToApply);
        }
Ejemplo n.º 21
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 syntaxTree      = codeDocument.GetSyntaxTree();
            var formattingSpans = syntaxTree.GetFormattingSpans();

            var result = new FormattingContext()
            {
                Uri = uri,
                OriginalSnapshot = originalSnapshot,
                CodeDocument     = codeDocument,
                Range            = range,
                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;
                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
                    {
                        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,
                        componentLambdaNestingLevel: 0);

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

            result.Indentations = indentations;

            return(result);
        }
Ejemplo n.º 22
0
        private TextEdit[] FilterCSharpTextEdits(FormattingContext context, TextEdit[] edits)
        {
            var filteredEdits = edits.Where(e => ShouldFormat(context, e.Range.Start, allowImplicitStatements: false)).ToArray();

            return(filteredEdits);
        }
Ejemplo n.º 23
0
        private static void CleanupSourceMappingStart(FormattingContext context, Range sourceMappingRange, List <TextChange> changes)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // bring the first line to its own line and adjust its indentation,
            //
            // E.g,
            //
            // @{   public int x = 0;
            // }
            //
            // becomes,
            //
            // @{
            //    public int x  = 0;
            // }
            //

            var text = context.SourceText;
            var sourceMappingSpan = sourceMappingRange.AsTextSpan(text);

            if (!ShouldFormat(context, sourceMappingSpan, allowImplicitStatements: false))
            {
                // We don't want to run cleanup on this range.
                return;
            }

            if (sourceMappingRange.Start.Character == 0)
            {
                // It already starts on a fresh new line which doesn't need cleanup.
                // E.g, (The mapping starts at | in the below case)
                // @{
                //     @: Some html
                // |   var x = 123;
                // }
                //

                return;
            }

            // @{
            //     if (true)
            //     {
            //         <div></div>|
            //
            //              |}
            // }
            // We want to return the length of the range marked by |...|
            //
            var whitespaceLength = text.GetFirstNonWhitespaceOffset(sourceMappingSpan, out var newLineCount);

            if (whitespaceLength == null)
            {
                // There was no content after the start of this mapping. Meaning it already is clean.
                // E.g,
                // @{|
                //    ...
                // }

                return;
            }

            var spanToReplace = new TextSpan(sourceMappingSpan.Start, whitespaceLength.Value);

            if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
            {
                // Can't find the correct indentation for this content. Leave it alone.
                return;
            }

            // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
            // Make sure to preserve the same number of blank lines as the original string had
            var replacement = PrependLines(context.GetIndentationLevelString(contentIndentLevel), context.NewLineString, Math.Max(newLineCount, 1));

            // After the below change the above example should look like,
            // @{
            //     if (true)
            //     {
            //         <div></div>
            //     }
            // }
            var change = new TextChange(spanToReplace, replacement);

            changes.Add(change);
        }
Ejemplo n.º 24
0
        public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken)
        {
            if (!context.IsFormatOnType || result.Kind != RazorLanguageKind.CSharp)
            {
                // We don't want to handle regular formatting or non-C# on type formatting here.
                return(result);
            }

            // Normalize and re-map the C# edits.
            var codeDocument    = context.CodeDocument;
            var csharpText      = SourceText.From(codeDocument.GetCSharpDocument().GeneratedCode);
            var normalizedEdits = NormalizeTextEdits(csharpText, result.Edits);
            var mappedEdits     = RemapTextEdits(codeDocument, normalizedEdits, result.Kind);
            var filteredEdits   = FilterCSharpTextEdits(context, mappedEdits);

            if (filteredEdits.Length == 0)
            {
                // There are no CSharp edits for us to apply. No op.
                return(new FormattingResult(filteredEdits));
            }

            // Find the lines that were affected by these edits.
            var originalText = codeDocument.GetSourceText();
            var changes      = filteredEdits.Select(e => e.AsTextChange(originalText));

            // Apply the format on type edits sent over by the client.
            var formattedText  = originalText.WithChanges(changes);
            var changedContext = await context.WithTextAsync(formattedText);

            TrackEncompassingChange(originalText, changes, out _, out var spanAfterFormatting);
            var rangeAfterFormatting = spanAfterFormatting.AsRange(formattedText);

            cancellationToken.ThrowIfCancellationRequested();

            // We make an optimistic attempt at fixing corner cases.
            var cleanupChanges = CleanupDocument(changedContext, rangeAfterFormatting);
            var cleanedText    = formattedText.WithChanges(cleanupChanges);

            changedContext = await changedContext.WithTextAsync(cleanedText);

            cancellationToken.ThrowIfCancellationRequested();

            // At this point we should have applied all edits that adds/removes newlines.
            // Let's now ensure the indentation of each of those lines is correct.

            // We only want to adjust the range that was affected.
            // We need to take into account the lines affected by formatting as well as cleanup.
            var cleanupLineDelta = LineDelta(formattedText, cleanupChanges);
            var rangeToAdjust    = new Range(rangeAfterFormatting.Start, new Position(rangeAfterFormatting.End.Line + cleanupLineDelta, 0));

            Debug.Assert(rangeToAdjust.End.IsValid(cleanedText), "Invalid range. This is unexpected.");

            var indentationChanges = AdjustIndentation(changedContext, cancellationToken, rangeToAdjust);

            if (indentationChanges.Count > 0)
            {
                // Apply the edits that modify indentation.
                cleanedText = cleanedText.WithChanges(indentationChanges);
            }

            // Now that we have made all the necessary changes to the document. Let's diff the original vs final version and return the diff.
            var finalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, cleanedText, lineDiffOnly: false);
            var finalEdits   = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray();

            return(new FormattingResult(finalEdits));
        }
Ejemplo n.º 25
0
        private static void CleanupSourceMappingEnd(FormattingContext context, Range sourceMappingRange, List <TextChange> changes)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // bring the content after the last line to its own line and adjust its indentation,
            //
            // E.g,
            //
            // @{
            //     if (true)
            //     {  <div></div>
            //     }
            // }
            //
            // becomes,
            //
            // @{
            //    if (true)
            //    {
            //        </div></div>
            //    }
            // }
            //

            var text = context.SourceText;
            var sourceMappingSpan   = sourceMappingRange.AsTextSpan(text);
            var mappingEndLineIndex = sourceMappingRange.End.Line;

            if (!context.Indentations[mappingEndLineIndex].StartsInCSharpContext)
            {
                // For corner cases like (Position marked with |),
                // It is already in a separate line. It doesn't need cleaning up.
                // @{
                //     if (true}
                //     {
                //         |<div></div>
                //     }
                // }
                //
                return;
            }

            var endSpan = TextSpan.FromBounds(sourceMappingSpan.End, sourceMappingSpan.End);

            if (!ShouldFormat(context, endSpan, allowImplicitStatements: false))
            {
                // We don't want to run cleanup on this range.
                return;
            }

            var contentStartOffset = text.Lines[mappingEndLineIndex].GetFirstNonWhitespaceOffset(sourceMappingRange.End.Character);

            if (contentStartOffset == null)
            {
                // There is no content after the end of this source mapping. No need to clean up.
                return;
            }

            var spanToReplace = new TextSpan(sourceMappingSpan.End, 0);

            if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
            {
                // Can't find the correct indentation for this content. Leave it alone.
                return;
            }

            // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
            var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel);

            // After the below change the above example should look like,
            // @{
            //     if (true)
            //     {
            //         <div></div>
            //     }
            // }
            var change = new TextChange(spanToReplace, replacement);

            changes.Add(change);
        }
Ejemplo n.º 26
0
 public abstract bool TryFormatOnType(Position position, FormattingContext context, out TextEdit[] edits);
Ejemplo n.º 27
0
        private List <TextChange> AdjustRazorIndentation(FormattingContext context)
        {
            // Assume HTML formatter has already run at this point and HTML is relatively indented correctly.
            // But HTML doesn't know about Razor blocks.
            // Our goal here is to indent each line according to the surrounding Razor blocks.
            var sourceText   = context.SourceText;
            var editsToApply = new List <TextChange>();

            for (var i = 0; i < sourceText.Lines.Count; i++)
            {
                var line = sourceText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // Empty line.
                    continue;
                }

                if (context.Indentations[i].StartsInCSharpContext)
                {
                    continue;
                }

                var razorDesiredIndentationLevel = context.Indentations[i].RazorIndentationLevel;
                if (razorDesiredIndentationLevel == 0)
                {
                    // This line isn't under any Razor specific constructs. Trust the HTML formatter.
                    continue;
                }

                var htmlDesiredIndentationLevel = context.Indentations[i].HtmlIndentationLevel;
                if (htmlDesiredIndentationLevel == 0 && !IsPartOfHtmlTag(context, context.Indentations[i].FirstSpan.Span.Start))
                {
                    // This line is under some Razor specific constructs but not under any HTML tag.
                    // E.g,
                    // @{
                    //          @* comment *@ <----
                    // }
                    //
                    // In this case, the HTML formatter wouldn't touch it but we should format it correctly.
                    // So, let's use our syntax understanding to rewrite the indentation.
                    // Note: This case doesn't apply for HTML tags (HTML formatter will touch it even if it is in the root).
                    // Hence the second part of the if condition.
                    //
                    var desiredIndentationLevel  = context.Indentations[i].IndentationLevel;
                    var desiredIndentationString = context.GetIndentationLevelString(desiredIndentationLevel);
                    var spanToReplace            = new TextSpan(line.Start, context.Indentations[i].ExistingIndentation);
                    var change = new TextChange(spanToReplace, desiredIndentationString);
                    editsToApply.Add(change);
                }
                else
                {
                    // This line is under some Razor specific constructs and HTML tags.
                    // E.g,
                    // @{
                    //    <div class="foo"
                    //         id="oof">  <----
                    //    </div>
                    // }
                    //
                    // In this case, the HTML formatter would've formatted it correctly. Let's not use our syntax understanding.
                    // Instead, we should just add to the existing indentation.
                    //
                    var razorDesiredIndentationString = context.GetIndentationLevelString(razorDesiredIndentationLevel);
                    var existingIndentationString     = context.GetIndentationString(context.Indentations[i].ExistingIndentationSize);
                    var desiredIndentationString      = existingIndentationString + razorDesiredIndentationString;
                    var spanToReplace = new TextSpan(line.Start, context.Indentations[i].ExistingIndentation);
                    var change        = new TextChange(spanToReplace, desiredIndentationString);
                    editsToApply.Add(change);
                }
            }

            return(editsToApply);
        }
        public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken)
        {
            if (!context.IsFormatOnType || result.Kind != RazorLanguageKind.CSharp)
            {
                // We don't want to handle regular formatting or non-C# on type formatting here.
                return(result);
            }

            // Normalize and re-map the C# edits.
            var codeDocument = context.CodeDocument;
            var csharpText   = codeDocument.GetCSharpSourceText();

            var textEdits = result.Edits;

            if (textEdits.Length == 0)
            {
                if (!DocumentMappingService.TryMapToProjectedDocumentPosition(codeDocument, context.HostDocumentIndex, out _, out var projectedIndex))
                {
                    _logger.LogWarning($"Failed to map to projected position for document {context.Uri}.");
                    return(result);
                }

                // Ask C# for formatting changes.
                var indentationOptions = new RazorIndentationOptions(
                    UseTabs: !context.Options.InsertSpaces,
                    TabSize: context.Options.TabSize,
                    IndentationSize: context.Options.TabSize);
                var autoFormattingOptions = new RazorAutoFormattingOptions(
                    formatOnReturn: true, formatOnTyping: true, formatOnSemicolon: true, formatOnCloseBrace: true);

                var formattingChanges = await RazorCSharpFormattingInteractionService.GetFormattingChangesAsync(
                    context.CSharpWorkspaceDocument,
                    typedChar : context.TriggerCharacter,
                    projectedIndex,
                    indentationOptions,
                    autoFormattingOptions,
                    indentStyle : CodeAnalysis.Formatting.FormattingOptions.IndentStyle.Smart,
                    cancellationToken).ConfigureAwait(false);

                if (formattingChanges.IsEmpty)
                {
                    _logger.LogInformation("Received no results.");
                    return(result);
                }

                textEdits = formattingChanges.Select(change => change.AsTextEdit(csharpText)).ToArray();
                _logger.LogInformation($"Received {textEdits.Length} results from C#.");
            }

            var normalizedEdits = NormalizeTextEdits(csharpText, textEdits, out var originalTextWithChanges);
            var mappedEdits     = RemapTextEdits(codeDocument, normalizedEdits, result.Kind);
            var filteredEdits   = FilterCSharpTextEdits(context, mappedEdits);

            if (filteredEdits.Length == 0)
            {
                // There are no CSharp edits for us to apply. No op.
                return(new FormattingResult(filteredEdits));
            }

            // Find the lines that were affected by these edits.
            var originalText = codeDocument.GetSourceText();
            var changes      = filteredEdits.Select(e => e.AsTextChange(originalText));

            // Apply the format on type edits sent over by the client.
            var formattedText  = ApplyChangesAndTrackChange(originalText, changes, out _, out var spanAfterFormatting);
            var changedContext = await context.WithTextAsync(formattedText);

            var rangeAfterFormatting = spanAfterFormatting.AsRange(formattedText);

            cancellationToken.ThrowIfCancellationRequested();

            // We make an optimistic attempt at fixing corner cases.
            var cleanupChanges = CleanupDocument(changedContext, rangeAfterFormatting);
            var cleanedText    = formattedText.WithChanges(cleanupChanges);

            changedContext = await changedContext.WithTextAsync(cleanedText);

            cancellationToken.ThrowIfCancellationRequested();

            // At this point we should have applied all edits that adds/removes newlines.
            // Let's now ensure the indentation of each of those lines is correct.

            // We only want to adjust the range that was affected.
            // We need to take into account the lines affected by formatting as well as cleanup.
            var lineDelta = LineDelta(formattedText, cleanupChanges, out var firstPosition, out var lastPosition);

            // Okay hear me out, I know this looks lazy, but it totally makes sense.
            // This method is called with edits that the C# formatter wants to make, and from those edits we work out which
            // other edits to apply etc. Fine, all good so far. BUT its totally possible that the user typed a closing brace
            // in the same position as the C# formatter thought it should be, on the line _after_ the code that the C# formatter
            // reformatted.
            //
            // For example, given:
            // if (true){
            //     }
            //
            // If the C# formatter is happy with the placement of that close brace then this method will get two edits:
            //  * On line 1 to indent the if by 4 spaces
            //  * On line 1 to add a newline and 4 spaces in front of the opening brace
            //
            // We'll happy format lines 1 and 2, and ignore the closing brace altogether. So, by looking one line further
            // we won't have that problem.
            if (rangeAfterFormatting.End.Line + lineDelta < cleanedText.Lines.Count)
            {
                lineDelta++;
            }

            // Now we know how many lines were affected by the cleanup and formatting, but we don't know where those lines are. For example, given:
            //
            // @if (true)
            // {
            //      }
            // else
            // {
            // $$}
            //
            // When typing that close brace, the changes would fix the previous close brace, but the line delta would be 0, so
            // we'd format line 6 and call it a day, even though the formatter made an edit on line 3. To fix this we use the
            // first and last position of edits made above, and make sure our range encompasses them as well. For convenience
            // we calculate these positions in the LineDelta method called above.
            // This is essentially: rangeToAdjust = new Range(Math.Min(firstFormattingEdit, userEdit), Math.Max(lastFormattingEdit, userEdit))
            var start = rangeAfterFormatting.Start;

            if (firstPosition is not null && firstPosition < start)
            {
                start = firstPosition;
            }

            var end = new Position(rangeAfterFormatting.End.Line + lineDelta, 0);

            if (lastPosition is not null && lastPosition < start)
            {
                end = lastPosition;
            }

            var rangeToAdjust = new Range(start, end);

            Debug.Assert(rangeToAdjust.End.IsValid(cleanedText), "Invalid range. This is unexpected.");

            var indentationChanges = await AdjustIndentationAsync(changedContext, cancellationToken, rangeToAdjust);

            if (indentationChanges.Count > 0)
            {
                // Apply the edits that modify indentation.
                cleanedText = cleanedText.WithChanges(indentationChanges);
            }

            // Now that we have made all the necessary changes to the document. Let's diff the original vs final version and return the diff.
            var finalChanges = cleanedText.GetTextChanges(originalText);
            var finalEdits   = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray();

            if (context.AutomaticallyAddUsings)
            {
                // Because we need to parse the C# code twice for this operation, lets do a quick check to see if its even necessary
                if (textEdits.Any(e => e.NewText.IndexOf("using") != -1))
                {
                    finalEdits = await AddUsingStatementEditsAsync(codeDocument, finalEdits, csharpText, originalTextWithChanges, cancellationToken);
                }
            }

            return(new FormattingResult(finalEdits));
        }