This commit is contained in:
David Wengier 2024-09-08 21:17:13 +10:00
Родитель fb2a2bd5e6
Коммит b275b91b26
4 изменённых файлов: 34 добавлений и 36 удалений

Просмотреть файл

@ -26,7 +26,7 @@ internal abstract class CSharpFormattingPassBase(IDocumentMappingService documen
protected IDocumentMappingService DocumentMappingService { get; } = documentMappingService;
public abstract Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> edits, CancellationToken cancellationToken);
public abstract Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> changes, CancellationToken cancellationToken);
protected async Task<List<TextChange>> AdjustIndentationAsync(FormattingContext context, CancellationToken cancellationToken, Range? range = null)
{
@ -54,7 +54,7 @@ internal abstract class CSharpFormattingPassBase(IDocumentMappingService documen
{
var mappingSpan = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
#if DEBUG
var spanText = context.SourceText.GetSubText(mappingSpan).ToString();
var spanText = context.SourceText.GetSubTextString(mappingSpan);
#endif
var options = new ShouldFormatOptions(

Просмотреть файл

@ -33,18 +33,18 @@ internal sealed class CSharpOnTypeFormattingPass(
{
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CSharpOnTypeFormattingPass>();
public async override Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> edits, CancellationToken cancellationToken)
public async override Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> changes, CancellationToken cancellationToken)
{
// Normalize and re-map the C# edits.
var codeDocument = context.CodeDocument;
var csharpText = codeDocument.GetCSharpSourceText();
if (edits.Length == 0)
if (changes.Length == 0)
{
if (!DocumentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), context.HostDocumentIndex, out _, out var projectedIndex))
{
_logger.LogWarning($"Failed to map to projected position for document {context.Uri}.");
return edits;
return changes;
}
// Ask C# for formatting changes.
@ -63,18 +63,18 @@ internal sealed class CSharpOnTypeFormattingPass(
if (formattingChanges.IsEmpty)
{
_logger.LogInformation($"Received no results.");
return edits;
return changes;
}
edits = formattingChanges;
_logger.LogInformation($"Received {edits.Length} results from C#.");
changes = formattingChanges;
_logger.LogInformation($"Received {changes.Length} results from C#.");
}
// Sometimes the C# document is out of sync with our document, so Roslyn can return edits to us that will throw when we try
// to normalize them. Instead of having this flow up and log a NFW, we just capture it here. Since this only happens when typing
// very quickly, it is a safe assumption that we'll get another chance to do on type formatting, since we know the user is typing.
// The proper fix for this is https://github.com/dotnet/razor-tooling/issues/6650 at which point this can be removed
foreach (var edit in edits)
foreach (var edit in changes)
{
var startPos = edit.Span.Start;
var endPos = edit.Span.End;
@ -82,32 +82,30 @@ internal sealed class CSharpOnTypeFormattingPass(
if (startPos >= count || endPos >= count)
{
_logger.LogWarning($"Got a bad edit that couldn't be applied. Edit is {startPos}-{endPos} but there are only {count} characters in C#.");
return edits;
return changes;
}
}
var normalizedEdits = csharpText.MinimizeTextChanges(edits, out var originalTextWithChanges);
var mappedEdits = RemapTextChanges(codeDocument, normalizedEdits);
var filteredEdits = FilterCSharpTextChanges(context, mappedEdits);
if (filteredEdits.Length == 0)
var normalizedChanges = csharpText.MinimizeTextChanges(changes, out var originalTextWithChanges);
var mappedChanges = RemapTextChanges(codeDocument, normalizedChanges);
var filteredChanges = FilterCSharpTextChanges(context, mappedChanges);
if (filteredChanges.Length == 0)
{
// There are no C# edits for us to apply that could be mapped, but we might still need to check for using statements
// because they are non mappable, but might be the only thing changed (eg from the Add Using code action)
//
// If there aren't any edits that are likely to contain using statement changes, this call will no-op.
filteredEdits = await AddUsingStatementEditsIfNecessaryAsync(context, codeDocument, csharpText, edits, originalTextWithChanges, filteredEdits, cancellationToken).ConfigureAwait(false);
filteredChanges = await AddUsingStatementEditsIfNecessaryAsync(context, codeDocument, csharpText, changes, originalTextWithChanges, filteredChanges, cancellationToken).ConfigureAwait(false);
return filteredEdits;
return filteredChanges;
}
// Find the lines that were affected by these edits.
var originalText = codeDocument.Source.Text;
_logger.LogTestOnly($"Original text:\r\n{originalText}");
var changes = filteredEdits;
// Apply the format on type edits sent over by the client.
var formattedText = ApplyChangesAndTrackChange(originalText, changes, out _, out var spanAfterFormatting);
var formattedText = ApplyChangesAndTrackChange(originalText, filteredChanges, out _, out var spanAfterFormatting);
_logger.LogTestOnly($"After C# changes:\r\n{formattedText}");
var changedContext = await context.WithTextAsync(formattedText).ConfigureAwait(false);
@ -194,7 +192,7 @@ internal sealed class CSharpOnTypeFormattingPass(
// 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).ToImmutableArray();
finalChanges = await AddUsingStatementEditsIfNecessaryAsync(context, codeDocument, csharpText, edits, originalTextWithChanges, finalChanges, cancellationToken).ConfigureAwait(false);
finalChanges = await AddUsingStatementEditsIfNecessaryAsync(context, codeDocument, csharpText, changes, originalTextWithChanges, finalChanges, cancellationToken).ConfigureAwait(false);
return finalChanges;
}
@ -239,9 +237,9 @@ internal sealed class CSharpOnTypeFormattingPass(
return newText;
}
private static ImmutableArray<TextChange> FilterCSharpTextChanges(FormattingContext context, ImmutableArray<TextChange> edits)
private static ImmutableArray<TextChange> FilterCSharpTextChanges(FormattingContext context, ImmutableArray<TextChange> changes)
{
return edits.WhereAsArray(e => ShouldFormat(context, e.Span, allowImplicitStatements: false));
return changes.WhereAsArray(e => ShouldFormat(context, e.Span, allowImplicitStatements: false));
}
private static int LineDelta(SourceText text, IEnumerable<TextChange> changes, out Position? firstPosition, out Position? lastPosition)

Просмотреть файл

@ -15,14 +15,14 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting;
/// </summary>
internal sealed class HtmlOnTypeFormattingPass(ILoggerFactory loggerFactory) : HtmlFormattingPassBase(loggerFactory.GetOrCreateLogger<HtmlOnTypeFormattingPass>())
{
public override Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> edits, CancellationToken cancellationToken)
public override Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext context, ImmutableArray<TextChange> changes, CancellationToken cancellationToken)
{
if (edits.Length == 0)
if (changes.Length == 0)
{
// There are no HTML edits for us to apply. No op.
return SpecializedTasks.EmptyImmutableArray<TextChange>();
}
return base.ExecuteAsync(context, edits, cancellationToken);
return base.ExecuteAsync(context, changes, cancellationToken);
}
}

Просмотреть файл

@ -102,17 +102,17 @@ public class FormattingTestBase : RazorToolingIntegrationTestBase
var htmlChanges = htmlEdits.SelectAsArray(source.GetTextChange);
// Act
var edits = await formattingService.GetDocumentFormattingChangesAsync(documentContext, htmlChanges, range, razorOptions, DisposalToken);
var changes = await formattingService.GetDocumentFormattingChangesAsync(documentContext, htmlChanges, range, razorOptions, DisposalToken);
// Assert
var edited = source.WithChanges(edits);
var edited = source.WithChanges(changes);
var actual = edited.ToString();
AssertEx.EqualOrDiff(expected, actual);
if (input.Equals(expected))
{
Assert.Empty(edits);
Assert.Empty(changes);
}
}
@ -153,10 +153,10 @@ public class FormattingTestBase : RazorToolingIntegrationTestBase
var documentContext = new DocumentContext(uri, documentSnapshot, projectContext: null);
// Act
ImmutableArray<TextChange> edits;
ImmutableArray<TextChange> changes;
if (languageKind == RazorLanguageKind.CSharp)
{
edits = await formattingService.GetCSharpOnTypeFormattingChangesAsync(documentContext, razorOptions, hostDocumentIndex: positionAfterTrigger, triggerCharacter: triggerCharacter, DisposalToken);
changes = await formattingService.GetCSharpOnTypeFormattingChangesAsync(documentContext, razorOptions, hostDocumentIndex: positionAfterTrigger, triggerCharacter: triggerCharacter, DisposalToken);
}
else
{
@ -166,25 +166,25 @@ public class FormattingTestBase : RazorToolingIntegrationTestBase
var htmlFormatter = new HtmlFormatter(client);
var htmlEdits = await htmlFormatter.GetDocumentFormattingEditsAsync(documentSnapshot, uri, options, DisposalToken);
var htmlChanges = htmlEdits.SelectAsArray(razorSourceText.GetTextChange);
edits = await formattingService.GetHtmlOnTypeFormattingChangesAsync(documentContext, htmlChanges, razorOptions, hostDocumentIndex: positionAfterTrigger, triggerCharacter: triggerCharacter, DisposalToken);
changes = await formattingService.GetHtmlOnTypeFormattingChangesAsync(documentContext, htmlChanges, razorOptions, hostDocumentIndex: positionAfterTrigger, triggerCharacter: triggerCharacter, DisposalToken);
}
// Assert
var edited = razorSourceText.WithChanges( edits);
var edited = razorSourceText.WithChanges( changes);
var actual = edited.ToString();
AssertEx.EqualOrDiff(expected, actual);
if (input.Equals(expected))
{
Assert.Empty(edits);
Assert.Empty(changes);
}
if (expectedChangedLines is not null)
{
var firstLine = edits.Min(e => razorSourceText.GetLinePositionSpan(e.Span).Start.Line);
var lastLine = edits.Max(e => razorSourceText.GetLinePositionSpan(e.Span).End.Line);
var delta = lastLine - firstLine + edits.Count(e => e.NewText.Contains(Environment.NewLine));
var firstLine = changes.Min(e => razorSourceText.GetLinePositionSpan(e.Span).Start.Line);
var lastLine = changes.Max(e => razorSourceText.GetLinePositionSpan(e.Span).End.Line);
var delta = lastLine - firstLine + changes.Count(e => e.NewText.Contains(Environment.NewLine));
Assert.Equal(expectedChangedLines.Value, delta + 1);
}
}