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 = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
private (RazorLanguageKind, TextEdit[]) GetFormattedEdits(RazorCodeDocument codeDocument, string expected, int positionBeforeTriggerChar) { var mappingService = new DefaultRazorDocumentMappingService(); var languageKind = mappingService.GetLanguageKind(codeDocument, positionBeforeTriggerChar); var expectedText = SourceText.From(expected); var(expectedCodeDocument, _) = CreateCodeDocumentAndSnapshot(expectedText, codeDocument.Source.FilePath, fileKind: codeDocument.GetFileKind()); var edits = Array.Empty <TextEdit>(); if (languageKind == RazorLanguageKind.CSharp) { var beforeCSharpText = SourceText.From(codeDocument.GetCSharpDocument().GeneratedCode); var afterCSharpText = SourceText.From(expectedCodeDocument.GetCSharpDocument().GeneratedCode); edits = SourceTextDiffer.GetMinimalTextChanges(beforeCSharpText, afterCSharpText, lineDiffOnly: false).Select(c => c.AsTextEdit(beforeCSharpText)).ToArray(); } else if (languageKind == RazorLanguageKind.Html) { var beforeHtmlText = SourceText.From(codeDocument.GetHtmlDocument().GeneratedHtml); var afterHtmlText = SourceText.From(expectedCodeDocument.GetHtmlDocument().GeneratedHtml); edits = SourceTextDiffer.GetMinimalTextChanges(beforeHtmlText, afterHtmlText, lineDiffOnly: false).Select(c => c.AsTextEdit(beforeHtmlText)).ToArray(); } return(languageKind, edits); }
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)); var changedText = originalText.WithChanges(changes); TrackEncompassingChange(originalText, changedText, out var spanBeforeChange, out var spanAfterChange); var rangeBeforeEdit = spanBeforeChange.AsRange(originalText); var rangeAfterEdit = spanAfterChange.AsRange(changedText); // Create a new formatting context for the changed razor document. var changedContext = await context.WithTextAsync(changedText); cancellationToken.ThrowIfCancellationRequested(); // Now, for each affected line in the edited version of the document, remove x amount of spaces // at the front to account for extra indentation applied by the C# formatter. // 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 indentationChanges = AdjustCSharpIndentation(changedContext, (int)rangeAfterEdit.Start.Line, (int)rangeAfterEdit.End.Line); if (indentationChanges.Count > 0) { // Apply the edits that modify indentation. changedText = changedText.WithChanges(indentationChanges); changedContext = await changedContext.WithTextAsync(changedText); } // We make an optimistic attempt at fixing corner cases. changedText = CleanupDocument(changedContext, rangeAfterEdit); // 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, changedText, lineDiffOnly: false); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
private static TextEdit[] NormalizeTextEdits(SourceText originalText, TextEdit[] edits) { var changes = edits.Select(e => e.AsTextChange(originalText)); var changedText = originalText.WithChanges(changes); var cleanChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var cleanEdits = cleanChanges.Select(c => c.AsTextEdit(originalText)).ToArray(); return(cleanEdits); }
public override async Task <TextEdit[]> FormatAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, Range range, FormattingOptions options, CancellationToken cancellationToken) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (documentSnapshot is null) { throw new ArgumentNullException(nameof(documentSnapshot)); } if (range is null) { throw new ArgumentNullException(nameof(range)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, _workspaceFactory); var originalText = context.SourceText; var result = new FormattingResult(Array.Empty <TextEdit>()); foreach (var pass in _formattingPasses) { cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } var filteredEdits = result.Edits.Where(e => range.LineOverlapsWith(e.Range)); // Make sure the edits actually change something, or its not worth responding var textChanges = filteredEdits.Select(e => e.AsTextChange(originalText)); var changedText = originalText.WithChanges(textChanges); if (changedText.ContentEquals(originalText)) { return(Array.Empty <TextEdit>()); } // Only send back the minimum edits var minimalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var finalEdits = minimalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(finalEdits); }
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 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(); var indentationChanges = await AdjustIndentationAsync(changedContext, cancellationToken); if (indentationChanges.Count > 0) { // Apply the edits that modify indentation. changedText = changedText.WithChanges(indentationChanges); } // Allow benchmarks to specify a different diff algorithm if (!context.Options.TryGetValue("UseSourceTextDiffer", out var useSourceTextDiffer)) { useSourceTextDiffer = new BooleanNumberString(false); } var finalChanges = useSourceTextDiffer.Bool ? SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false) : 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) { // We don't want to handle OnTypeFormatting here. return(result); } var originalText = context.SourceText; var htmlEdits = await HtmlFormatter.FormatAsync(context, cancellationToken); // Allow benchmarks to specify a different diff algorithm if (!context.Options.TryGetValue("UseSourceTextDiffer", out var useSourceTextDiffer)) { useSourceTextDiffer = new BooleanNumberString(false); } var normalizedEdits = htmlEdits; if (useSourceTextDiffer.Bool) { 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 = useSourceTextDiffer.Bool ? SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false) : changedText.GetTextChanges(originalText); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }
protected static TextEdit[] NormalizeTextEdits(SourceText originalText, TextEdit[] edits) { if (originalText is null) { throw new ArgumentNullException(nameof(originalText)); } if (edits is null) { throw new ArgumentNullException(nameof(edits)); } var changes = edits.Select(e => e.AsTextChange(originalText)); var changedText = originalText.WithChanges(changes); var cleanChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var cleanEdits = cleanChanges.Select(c => c.AsTextEdit(originalText)).ToArray(); return(cleanEdits); }
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 void CharDiff_LargeFile_OneCharChanged() { SourceTextDiffer.GetMinimalTextChanges(_largeFileOriginal, _largeFileMinimalChanges, lineDiffOnly: false); }
public void LineDiff_LargeFile_SignificantlyDifferent() { SourceTextDiffer.GetMinimalTextChanges(_largeFileOriginal, _largeFileSignificantChanges, lineDiffOnly: true); }
private RazorDocumentRangeFormattingResponse Format(RazorDocumentRangeFormattingParams @params) { if (@params.Kind == RazorLanguageKind.Razor) { throw new InvalidOperationException("We shouldn't be asked to format Razor language kind."); } var options = @params.Options; var response = new RazorDocumentRangeFormattingResponse(); if (@params.Kind == RazorLanguageKind.CSharp) { var codeDocument = _documents[@params.HostDocumentFilePath]; var csharpSourceText = codeDocument.GetCSharpSourceText(); var csharpDocument = GetCSharpDocument(codeDocument, @params.Options); if (!csharpDocument.TryGetSyntaxRoot(out var root)) { throw new InvalidOperationException("Couldn't get syntax root."); } var spanToFormat = @params.ProjectedRange.AsTextSpan(csharpSourceText); var changes = Formatter.GetFormattedTextChanges(root, spanToFormat, csharpDocument.Project.Solution.Workspace); response.Edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray(); } else if (@params.Kind == RazorLanguageKind.Html) { response.Edits = Array.Empty <TextEdit>(); var codeDocument = _documents[@params.HostDocumentFilePath]; var generatedHtml = codeDocument.GetHtmlDocument().GeneratedHtml; generatedHtml = generatedHtml.Replace("\r", "", StringComparison.Ordinal).Replace("\n", "\r\n", StringComparison.Ordinal); // Get formatted baseline file var baselineInputFileName = Path.ChangeExtension(_baselineFileName, ".input.html"); var baselineOutputFileName = Path.ChangeExtension(_baselineFileName, ".output.html"); var baselineInputFile = TestFile.Create(baselineInputFileName, GetType().GetTypeInfo().Assembly); var baselineOutputFile = TestFile.Create(baselineOutputFileName, GetType().GetTypeInfo().Assembly); if (GenerateBaselines) { if (baselineInputFile.Exists()) { // If it already exists, we only want to update if the input is different. var inputContent = baselineInputFile.ReadAllText(); if (string.Equals(inputContent, generatedHtml, StringComparison.Ordinal)) { return(response); } } var baselineInputFilePath = Path.Combine(_projectPath, baselineInputFileName); File.WriteAllText(baselineInputFilePath, generatedHtml); var baselineOutputFilePath = Path.Combine(_projectPath, baselineOutputFileName); File.WriteAllText(baselineOutputFilePath, generatedHtml); return(response); } if (!baselineInputFile.Exists()) { throw new XunitException($"The resource {baselineInputFileName} was not found."); } if (!baselineOutputFile.Exists()) { throw new XunitException($"The resource {baselineOutputFileName} was not found."); } var baselineInputHtml = baselineInputFile.ReadAllText(); if (!string.Equals(baselineInputHtml, generatedHtml, StringComparison.Ordinal)) { throw new XunitException($"The baseline for {_baselineFileName} is out of date."); } var baselineOutputHtml = baselineOutputFile.ReadAllText(); var baselineInputText = SourceText.From(baselineInputHtml); var baselineOutputText = SourceText.From(baselineOutputHtml); var changes = SourceTextDiffer.GetMinimalTextChanges(baselineInputText, baselineOutputText, lineDiffOnly: false); var edits = changes.Select(c => c.AsTextEdit(baselineInputText)).ToArray(); response.Edits = edits; } return(response); }
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)); }
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); // 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++; } var rangeToAdjust = new Range(rangeAfterFormatting.Start, new Position(rangeAfterFormatting.End.Line + lineDelta, 0)); 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 = SourceTextDiffer.GetMinimalTextChanges(originalText, cleanedText, lineDiffOnly: false); var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(new FormattingResult(finalEdits)); }