Skip to content

Commit

Permalink
Revert "Apply changes directly to text buffer (#62337)" (#62589)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed Jul 13, 2022
1 parent 4e7b392 commit 0a39dcf
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Indentation;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Formatting;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
Expand Down Expand Up @@ -100,7 +99,18 @@ private static async Task TokenFormatWorkerAsync(TestWorkspace workspace, ITextB
var formatter = new CSharpSmartTokenFormatter(options, rules, (CompilationUnitSyntax)documentSyntax.Root, documentSyntax.Text);
var changes = formatter.FormatToken(token, CancellationToken.None);

buffer.ApplyChanges(changes);
ApplyChanges(buffer, changes);
}

private static void ApplyChanges(ITextBuffer buffer, IList<TextChange> changes)
{
using var edit = buffer.CreateEdit();
foreach (var change in changes)
{
edit.Replace(change.Span.ToSpan(), change.NewText);
}

edit.Apply();
}

protected static async Task<int> GetSmartTokenFormatterIndentationAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,39 +125,49 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom
/// </summary>
private void ApplyEdits(Document document, ITextView textView, ITextBuffer subjectBuffer, string title, CommentSelectionResult edits, CancellationToken cancellationToken)
{
var originalSnapshot = subjectBuffer.CurrentSnapshot;
var workspace = document.Project.Solution.Workspace;

// Create tracking spans to track the text changes.
var currentSnapshot = subjectBuffer.CurrentSnapshot;
var trackingSpans = edits.TrackingSpans
.SelectAsArray(textSpan => (originalSpan: textSpan, trackingSpan: CreateTrackingSpan(edits.ResultOperation, currentSnapshot, textSpan.TrackingTextSpan)));

// Apply the text changes.
SourceText newText;
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
{
subjectBuffer.ApplyChanges(edits.TextChanges);
var oldSolution = workspace.CurrentSolution;

var oldDocument = oldSolution.GetRequiredDocument(document.Id);
var oldText = oldDocument.GetTextSynchronously(cancellationToken);
newText = oldText.WithChanges(edits.TextChanges.Distinct());

var newSolution = oldSolution.WithDocumentText(document.Id, newText, PreservationMode.PreserveIdentity);
workspace.TryApplyChanges(newSolution);

transaction.Complete();
}

if (edits.TrackingSpans.Any())
{
// Create tracking spans to track the text changes.
var trackingSpans = edits.TrackingSpans
.SelectAsArray(textSpan => (originalSpan: textSpan, trackingSpan: CreateTrackingSpan(edits.ResultOperation, originalSnapshot, textSpan.TrackingTextSpan)));

// Convert the tracking spans into snapshot spans for formatting and selection.
var trackingSnapshotSpans = trackingSpans.Select(s => CreateSnapshotSpan(subjectBuffer.CurrentSnapshot, s.trackingSpan, s.originalSpan));
// Convert the tracking spans into snapshot spans for formatting and selection.
var trackingSnapshotSpans = trackingSpans.Select(s => CreateSnapshotSpan(subjectBuffer.CurrentSnapshot, s.trackingSpan, s.originalSpan));

if (trackingSnapshotSpans.Any())
{
if (edits.ResultOperation == Operation.Uncomment && document.SupportsSyntaxTree)
{
// Format the document only during uncomment operations. Use second transaction so it can be undone.
using var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService);

var newText = subjectBuffer.CurrentSnapshot.AsText();
var oldSyntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken);
var newRoot = oldSyntaxTree.WithChangedText(newText).GetRoot(cancellationToken);

var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false);
var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(newRoot, change.Span.ToTextSpan()));
var formattedChanges = Formatter.GetFormattedTextChanges(newRoot, formattingSpans, document.Project.Solution.Workspace.Services, formattingOptions, rules: null, cancellationToken);

subjectBuffer.ApplyChanges(formattedChanges);
var updatedDocument = workspace.CurrentSolution.GetRequiredDocument(document.Id);
var root = updatedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken);

var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(root, change.Span.ToTextSpan()));
var formattedRoot = Formatter.Format(root, formattingSpans, workspace.Services, formattingOptions, rules: null, cancellationToken);
var formattedDocument = document.WithSyntaxRoot(formattedRoot);

workspace.ApplyDocumentChanges(formattedDocument, cancellationToken);
transaction.Complete();
}

Expand Down
10 changes: 4 additions & 6 deletions src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos
return;
}

var subjectBuffer = args.SubjectBuffer;

var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document == null)
{
return;
Expand Down Expand Up @@ -88,16 +86,16 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos
}

var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive);
var span = trackingSpan.GetSpan(subjectBuffer.CurrentSnapshot).Span.ToTextSpan();
var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan();

// Note: C# always completes synchronously, TypeScript is async
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, args.SubjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
if (changes.IsEmpty)
{
return;
}

subjectBuffer.ApplyChanges(changes);
solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
}
}
}
33 changes: 20 additions & 13 deletions src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,28 @@ private void Format(ITextView textView, ITextBuffer textBuffer, Document documen
return;
}

if (selectionOpt.HasValue)
{
var ruleFactory = document.Project.Solution.Workspace.Services.GetRequiredService<IHostDependentFormattingRuleFactoryService>();
changes = ruleFactory.FilterFormattedChanges(document.Id, selectionOpt.Value, changes).ToImmutableArray();
}
var workspace = document.Project.Solution.Workspace;
ApplyChanges(workspace, document.Id, changes, selectionOpt, cancellationToken);
transaction.Complete();
}
}

private static void ApplyChanges(Workspace workspace, DocumentId documentId, IList<TextChange> changes, TextSpan? selectionOpt, CancellationToken cancellationToken)
{
if (selectionOpt.HasValue)
{
var ruleFactory = workspace.Services.GetRequiredService<IHostDependentFormattingRuleFactoryService>();

if (!changes.IsEmpty)
changes = ruleFactory.FilterFormattedChanges(documentId, selectionOpt.Value, changes).ToList();
if (changes.Count == 0)
{
using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
{
textBuffer.ApplyChanges(changes);
}
return;
}
}

transaction.Complete();
using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
{
workspace.ApplyTextChanges(documentId, changes, cancellationToken);
}
}

Expand Down Expand Up @@ -184,7 +191,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Automatic_Formatting))
{
transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;
subjectBuffer.ApplyChanges(textChanges);
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, textChanges, cancellationToken);
transaction.Complete();
}

Expand All @@ -195,7 +202,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
return;
}

var snapshotAfterFormatting = subjectBuffer.CurrentSnapshot;
var snapshotAfterFormatting = args.SubjectBuffer.CurrentSnapshot;

var oldCaretPosition = caretPosition.Value.TranslateTo(snapshotAfterFormatting, PointTrackingMode.Negative);
var newCaretPosition = newCaretPositionMarker.Value.TranslateTo(snapshotAfterFormatting, PointTrackingMode.Negative);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,59 +249,58 @@ private AsyncCompletionData.CommitResult Commit(
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None);
}

ITextSnapshot updatedCurrentSnapshot;
using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null))
{
edit.Replace(mappedSpan.Span, change.TextChange.NewText);

// edit.Apply() may trigger changes made by extensions.
// updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions.
updatedCurrentSnapshot = edit.Apply();
}
var updatedCurrentSnapshot = edit.Apply();

if (change.NewPosition.HasValue)
{
// Roslyn knows how to position the caret in the snapshot we just created.
// If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one.
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value));
}
else
{
// Or, If we're doing a minimal change, then the edit that we make to the
// buffer may not make the total text change that places the caret where we
// would expect it to go based on the requested change. In this case,
// determine where the item should go and set the care manually.

// Note: we only want to move the caret if the caret would have been moved
// by the edit. i.e. if the caret was actually in the mapped span that
// we're replacing.
var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer);
if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value))
if (change.NewPosition.HasValue)
{
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0));
// Roslyn knows how to position the caret in the snapshot we just created.
// If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one.
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value));
}
else
{
view.Caret.EnsureVisible();
// Or, If we're doing a minimal change, then the edit that we make to the
// buffer may not make the total text change that places the caret where we
// would expect it to go based on the requested change. In this case,
// determine where the item should go and set the care manually.

// Note: we only want to move the caret if the caret would have been moved
// by the edit. i.e. if the caret was actually in the mapped span that
// we're replacing.
var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer);
if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value))
{
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0));
}
else
{
view.Caret.EnsureVisible();
}
}
}

includesCommitCharacter = change.IncludesCommitCharacter;

if (roslynItem.Rules.FormatOnCommit)
{
// The edit updates the snapshot however other extensions may make changes there.
// Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above.
var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
var formattingService = currentDocument?.GetRequiredLanguageService<IFormattingInteractionService>();
includesCommitCharacter = change.IncludesCommitCharacter;

if (currentDocument != null && formattingService != null)
if (roslynItem.Rules.FormatOnCommit)
{
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
// The edit updates the snapshot however other extensions may make changes there.
// Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above.
var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
var formattingService = currentDocument?.GetRequiredLanguageService<IFormattingInteractionService>();

// Note: C# always completes synchronously, TypeScript is async
var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
subjectBuffer.ApplyChanges(changes);
if (currentDocument != null && formattingService != null)
{
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);

// Note: C# always completes synchronously, TypeScript is async
var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken);
}
}
}

Expand Down
9 changes: 0 additions & 9 deletions src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Formatting;
Expand Down Expand Up @@ -68,14 +67,6 @@ public static IndentationOptions GetIndentationOptions(this ITextBuffer textBuff
};
}

public static AddImportPlacementOptions GetAddImportPlacementOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, HostLanguageServices languageServices, bool allowInHiddenRegions)
{
var editorOptions = optionsProvider.Factory.GetOptions(textBuffer);
var configOptions = new EditorAnalyzerConfigOptions(editorOptions);
var fallbackOptions = optionsProvider.GlobalOptions.GetAddImportPlacementOptions(languageServices);
return configOptions.GetAddImportPlacementOptions(allowInHiddenRegions, fallbackOptions, languageServices);
}

public static IndentingStyle ToEditorIndentStyle(this FormattingOptions2.IndentStyle value)
=> value switch
{
Expand Down
25 changes: 0 additions & 25 deletions src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;

namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions
Expand Down Expand Up @@ -61,28 +59,5 @@ private static bool TryGetSupportsFeatureService(ITextBuffer buffer, [NotNullWhe

return service != null;
}

public static ITextSnapshot ApplyChange(this ITextBuffer buffer, TextChange change)
{
using var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null);
edit.Replace(change.Span.ToSpan(), change.NewText);
return edit.Apply();
}

public static ITextSnapshot ApplyChanges(this ITextBuffer buffer, IEnumerable<TextChange> changes)
{
using var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null);
return ApplyChanges(edit, changes);
}

public static ITextSnapshot ApplyChanges(this ITextEdit edit, IEnumerable<TextChange> changes)
{
foreach (var change in changes)
{
edit.Replace(change.Span.ToSpan(), change.NewText);
}

return edit.Apply();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void FormatAndApplyToBuffer(

using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
{
textBuffer.ApplyChanges(changes);
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Operations
Imports Microsoft.VisualStudio.Text.Projection
Imports Roslyn.Utilities

Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
<[UseExportProvider]>
Expand Down
Loading

0 comments on commit 0a39dcf

Please sign in to comment.