public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (result.Kind != RazorLanguageKind.Razor) { // We don't care about changes to projected documents here. return(result); } var originalDiagnostics = context.CodeDocument.GetSyntaxTree().Diagnostics; var text = context.SourceText; var edits = result.Edits; var changes = edits.Select(e => e.AsTextChange(text)); var changedText = text.WithChanges(changes); var changedContext = await context.WithTextAsync(changedText); var changedDiagnostics = changedContext.CodeDocument.GetSyntaxTree().Diagnostics; if (!originalDiagnostics.SequenceEqual(changedDiagnostics)) { // Looks like we removed some non-whitespace content as part of formatting. Oops. // Discard this formatting result. if (DebugAssertsEnabled) { Debug.Fail("A formatting result was rejected because the formatted text produced different diagnostics compared to the original text."); } return(new FormattingResult(Array.Empty <TextEdit>())); } return(result); }
public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (context.IsFormatOnType || result.Kind != RazorLanguageKind.Razor) { // We don't want to handle OnTypeFormatting here. return(result); } // Apply previous edits if any. var originalText = context.SourceText; var changedText = originalText; var changedContext = context; if (result.Edits.Length > 0) { var changes = result.Edits.Select(e => e.AsTextChange(originalText)).ToArray(); changedText = changedText.WithChanges(changes); changedContext = await context.WithTextAsync(changedText); } cancellationToken.ThrowIfCancellationRequested(); // Apply original C# edits var csharpEdits = await FormatCSharpAsync(changedContext, cancellationToken); if (csharpEdits.Count > 0) { var csharpChanges = csharpEdits.Select(c => c.AsTextChange(changedText)); changedText = changedText.WithChanges(csharpChanges); changedContext = await changedContext.WithTextAsync(changedText); } cancellationToken.ThrowIfCancellationRequested(); // We make an optimistic attempt at fixing corner cases. var cleanupChanges = CleanupDocument(changedContext); changedText = changedText.WithChanges(cleanupChanges); changedContext = await changedContext.WithTextAsync(changedText); cancellationToken.ThrowIfCancellationRequested(); var indentationChanges = await AdjustIndentationAsync(changedContext, cancellationToken); if (indentationChanges.Count > 0) { // Apply the edits that modify indentation. changedText = changedText.WithChanges(indentationChanges); } var finalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
public virtual Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { return(Task.FromResult(Execute(context, result))); }
public virtual FormattingResult Execute(FormattingContext context, FormattingResult result) { return(result); }
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)); }
public abstract Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken);
public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (context.IsFormatOnType || result.Kind != RazorLanguageKind.Razor) { // We don't want to handle OnTypeFormatting here. return(result); } // Apply previous edits if any. var originalText = context.SourceText; var changedText = originalText; var changedContext = context; if (result.Edits.Length > 0) { var changes = result.Edits.Select(e => e.AsTextChange(originalText)).ToArray(); changedText = changedText.WithChanges(changes); changedContext = await context.WithTextAsync(changedText); } cancellationToken.ThrowIfCancellationRequested(); // Apply original C# edits var csharpEdits = await FormatCSharpAsync(changedContext, cancellationToken); if (csharpEdits.Count > 0) { var csharpChanges = csharpEdits.Select(c => c.AsTextChange(changedText)); changedText = changedText.WithChanges(csharpChanges); changedContext = await changedContext.WithTextAsync(changedText); _logger.LogTestOnly($"After FormatCSharpAsync:\r\n{changedText}"); } cancellationToken.ThrowIfCancellationRequested(); var indentationChanges = await AdjustIndentationAsync(changedContext, cancellationToken); if (indentationChanges.Count > 0) { // Apply the edits that modify indentation. changedText = changedText.WithChanges(indentationChanges); _logger.LogTestOnly($"After AdjustIndentationAsync:\r\n{changedText}"); } _logger.LogTestOnly($"Generated C#:\r\n{context.CSharpSourceText}"); var finalChanges = changedText.GetTextChanges(originalText); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
private bool FormatsOutsidePureCSharpDirectiveBlocks(FormattingContext context, FormattingResult result) { var text = context.SourceText; var changes = result.Edits.Select(e => e.AsTextChange(text)); var changedText = text.WithChanges(changes); var affectedSpan = changedText.GetEncompassingTextChangeRange(text).Span; var affectedRange = affectedSpan.AsRange(text); var syntaxTree = context.CodeDocument.GetSyntaxTree(); var nodes = syntaxTree.GetCodeBlockDirectives(); var affectedCodeDirective = nodes.FirstOrDefault(n => { var range = n.GetRange(context.CodeDocument.Source); return(range.Contains(affectedRange)); }); if (affectedCodeDirective == null) { // This edit lies outside any C# directive blocks. return(true); } if (!(affectedCodeDirective.Body is RazorDirectiveBodySyntax directiveBody)) { // This can't happen realistically. Just being defensive. return(false); } // 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 check. return(false); } if (ContainsNonCSharpContent(innerCodeBlockNode)) { // We currently don't support formatting code block directives with Markup or other Razor constructs. _logger.LogDebug("A formatting result was rejected because it was going to format code directive with mixed content."); return(true); } return(false); }
public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (context.IsFormatOnType) { // We don't want to handle OnTypeFormatting here. return(result); } var originalText = context.SourceText; var htmlEdits = await HtmlFormatter.FormatAsync(context, context.Range, cancellationToken); var normalizedEdits = NormalizeTextEdits(originalText, htmlEdits); var mappedEdits = RemapTextEdits(context.CodeDocument, normalizedEdits, RazorLanguageKind.Html); var changes = mappedEdits.Select(e => e.AsTextChange(originalText)); var changedText = originalText; var changedContext = context; if (changes.Any()) { changedText = originalText.WithChanges(changes); // Create a new formatting context for the changed razor document. changedContext = await context.WithTextAsync(changedText); } var indentationChanges = AdjustRazorIndentation(changedContext); if (indentationChanges.Count > 0) { // Apply the edits that adjust indentation. changedText = changedText.WithChanges(indentationChanges); } var finalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result) { if (context.IsFormatOnType) { // We don't want to handle OnTypeFormatting here. return(result); } Debug.Assert(result.Edits.Length == 0, "CodeBlockDirectiveFormatter should be invoked first."); var edits = RemapTextEdits(context.CodeDocument, result.Edits, result.Kind); // 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.Count - 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 newEdits = new List <TextEdit>(); FormatCodeBlockStart(context, changedText, directiveBody, innerCodeBlockNode, newEdits); FormatCodeBlockEnd(context, changedText, directiveBody, innerCodeBlockNode, newEdits); changedText = ApplyChanges(changedText, newEdits.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); } var normalizedEdits = NormalizeTextEdits(context.SourceText, allEdits.ToArray()); return(new FormattingResult(normalizedEdits)); }
public async override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (context.IsFormatOnType) { // We don't want to handle OnTypeFormatting here. return(result); } // Apply previous edits if any. var originalText = context.SourceText; var changedText = originalText; var changedContext = context; if (result.Edits.Length > 0) { var changes = result.Edits.Select(e => e.AsTextChange(originalText)).ToArray(); changedText = changedText.WithChanges(changes); changedContext = await context.WithTextAsync(changedText); cancellationToken.ThrowIfCancellationRequested(); } // Format the razor bits of the file var syntaxTree = changedContext.CodeDocument.GetSyntaxTree(); var edits = FormatRazor(changedContext, syntaxTree); // Compute the final combined set of edits var formattingChanges = edits.Select(e => e.AsTextChange(changedText)); changedText = changedText.WithChanges(formattingChanges); var finalChanges = changedText.GetTextChanges(originalText); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
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)); }
public override Task <FormattingResult> ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) { if (result.Kind != RazorLanguageKind.Razor) { // We don't care about changes to projected documents here. return(Task.FromResult(result)); } var text = context.SourceText; var edits = result.Edits; var changes = edits.Select(e => e.AsTextChange(text)); var changedText = text.WithChanges(changes); if (!text.NonWhitespaceContentEquals(changedText)) { // Looks like we removed some non-whitespace content as part of formatting. Oops. // Discard this formatting result. if (DebugAssertsEnabled) { Debug.Fail("A formatting result was rejected because it was going to change non-whitespace content in the document."); } return(Task.FromResult(new FormattingResult(Array.Empty <TextEdit>()))); } return(Task.FromResult(result)); }