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); }
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()); }