private async Task <List <TextEdit> > FormatCSharpAsync(FormattingContext context, CancellationToken cancellationToken)
        {
            var sourceText  = context.SourceText;
            var csharpEdits = new List <TextEdit>();

            foreach (var mapping in context.CodeDocument.GetCSharpDocument().SourceMappings)
            {
                var span = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                if (!ShouldFormat(context, span, allowImplicitStatements: true))
                {
                    // We don't want to format this range.
                    continue;
                }

                // These should already be remapped.
                var range = span.AsRange(sourceText);
                var edits = await CSharpFormatter.FormatAsync(context, range, cancellationToken);

                csharpEdits.AddRange(edits.Where(e => range.Contains(e.Range)));
            }

            return(csharpEdits);
        }
Example #2
0
        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;
                }

                var directiveRange = directive.GetRange(source);
                if (!directiveRange.OverlapsWith(context.Range))
                {
                    // This block isn't in the selected range.
                    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 csharprangeToFormat = innerCodeBlockRange.Overlap(context.Range);
                if (csharprangeToFormat != null)
                {
                    var codeEdits = await _csharpFormatter.FormatAsync(context.CodeDocument, csharprangeToFormat, 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());
        }