Skip to content

Commit 0a39dcf

Browse files
authored
Revert "Apply changes directly to text buffer (dotnet#62337)" (dotnet#62589)
This reverts commit 002abc1. Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1568401
1 parent 4e7b392 commit 0a39dcf

File tree

13 files changed

+135
-135
lines changed

13 files changed

+135
-135
lines changed

src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
using Microsoft.CodeAnalysis.CSharp.Formatting;
1515
using Microsoft.CodeAnalysis.CSharp.Indentation;
1616
using Microsoft.CodeAnalysis.CSharp.Syntax;
17-
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
1817
using Microsoft.CodeAnalysis.Editor.UnitTests;
1918
using Microsoft.CodeAnalysis.Editor.UnitTests.Formatting;
2019
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
@@ -100,7 +99,18 @@ private static async Task TokenFormatWorkerAsync(TestWorkspace workspace, ITextB
10099
var formatter = new CSharpSmartTokenFormatter(options, rules, (CompilationUnitSyntax)documentSyntax.Root, documentSyntax.Text);
101100
var changes = formatter.FormatToken(token, CancellationToken.None);
102101

103-
buffer.ApplyChanges(changes);
102+
ApplyChanges(buffer, changes);
103+
}
104+
105+
private static void ApplyChanges(ITextBuffer buffer, IList<TextChange> changes)
106+
{
107+
using var edit = buffer.CreateEdit();
108+
foreach (var change in changes)
109+
{
110+
edit.Replace(change.Span.ToSpan(), change.NewText);
111+
}
112+
113+
edit.Apply();
104114
}
105115

106116
protected static async Task<int> GetSmartTokenFormatterIndentationAsync(

src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs

+27-17
Original file line numberDiff line numberDiff line change
@@ -125,39 +125,49 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom
125125
/// </summary>
126126
private void ApplyEdits(Document document, ITextView textView, ITextBuffer subjectBuffer, string title, CommentSelectionResult edits, CancellationToken cancellationToken)
127127
{
128-
var originalSnapshot = subjectBuffer.CurrentSnapshot;
128+
var workspace = document.Project.Solution.Workspace;
129+
130+
// Create tracking spans to track the text changes.
131+
var currentSnapshot = subjectBuffer.CurrentSnapshot;
132+
var trackingSpans = edits.TrackingSpans
133+
.SelectAsArray(textSpan => (originalSpan: textSpan, trackingSpan: CreateTrackingSpan(edits.ResultOperation, currentSnapshot, textSpan.TrackingTextSpan)));
129134

130135
// Apply the text changes.
136+
SourceText newText;
131137
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
132138
{
133-
subjectBuffer.ApplyChanges(edits.TextChanges);
139+
var oldSolution = workspace.CurrentSolution;
140+
141+
var oldDocument = oldSolution.GetRequiredDocument(document.Id);
142+
var oldText = oldDocument.GetTextSynchronously(cancellationToken);
143+
newText = oldText.WithChanges(edits.TextChanges.Distinct());
144+
145+
var newSolution = oldSolution.WithDocumentText(document.Id, newText, PreservationMode.PreserveIdentity);
146+
workspace.TryApplyChanges(newSolution);
147+
134148
transaction.Complete();
135149
}
136150

137-
if (edits.TrackingSpans.Any())
138-
{
139-
// Create tracking spans to track the text changes.
140-
var trackingSpans = edits.TrackingSpans
141-
.SelectAsArray(textSpan => (originalSpan: textSpan, trackingSpan: CreateTrackingSpan(edits.ResultOperation, originalSnapshot, textSpan.TrackingTextSpan)));
142-
143-
// Convert the tracking spans into snapshot spans for formatting and selection.
144-
var trackingSnapshotSpans = trackingSpans.Select(s => CreateSnapshotSpan(subjectBuffer.CurrentSnapshot, s.trackingSpan, s.originalSpan));
151+
// Convert the tracking spans into snapshot spans for formatting and selection.
152+
var trackingSnapshotSpans = trackingSpans.Select(s => CreateSnapshotSpan(subjectBuffer.CurrentSnapshot, s.trackingSpan, s.originalSpan));
145153

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

151-
var newText = subjectBuffer.CurrentSnapshot.AsText();
152-
var oldSyntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken);
153-
var newRoot = oldSyntaxTree.WithChangedText(newText).GetRoot(cancellationToken);
154-
155161
var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false);
156-
var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(newRoot, change.Span.ToTextSpan()));
157-
var formattedChanges = Formatter.GetFormattedTextChanges(newRoot, formattingSpans, document.Project.Solution.Workspace.Services, formattingOptions, rules: null, cancellationToken);
158162

159-
subjectBuffer.ApplyChanges(formattedChanges);
163+
var updatedDocument = workspace.CurrentSolution.GetRequiredDocument(document.Id);
164+
var root = updatedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken);
165+
166+
var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(root, change.Span.ToTextSpan()));
167+
var formattedRoot = Formatter.Format(root, formattingSpans, workspace.Services, formattingOptions, rules: null, cancellationToken);
168+
var formattedDocument = document.WithSyntaxRoot(formattedRoot);
160169

170+
workspace.ApplyDocumentChanges(formattedDocument, cancellationToken);
161171
transaction.Complete();
162172
}
163173

src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos
5555
return;
5656
}
5757

58-
var subjectBuffer = args.SubjectBuffer;
59-
60-
var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
58+
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
6159
if (document == null)
6260
{
6361
return;
@@ -88,16 +86,16 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos
8886
}
8987

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

9391
// Note: C# always completes synchronously, TypeScript is async
94-
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
92+
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, args.SubjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
9593
if (changes.IsEmpty)
9694
{
9795
return;
9896
}
9997

100-
subjectBuffer.ApplyChanges(changes);
98+
solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
10199
}
102100
}
103101
}

src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs

+20-13
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,28 @@ private void Format(ITextView textView, ITextBuffer textBuffer, Document documen
7575
return;
7676
}
7777

78-
if (selectionOpt.HasValue)
79-
{
80-
var ruleFactory = document.Project.Solution.Workspace.Services.GetRequiredService<IHostDependentFormattingRuleFactoryService>();
81-
changes = ruleFactory.FilterFormattedChanges(document.Id, selectionOpt.Value, changes).ToImmutableArray();
82-
}
78+
var workspace = document.Project.Solution.Workspace;
79+
ApplyChanges(workspace, document.Id, changes, selectionOpt, cancellationToken);
80+
transaction.Complete();
81+
}
82+
}
83+
84+
private static void ApplyChanges(Workspace workspace, DocumentId documentId, IList<TextChange> changes, TextSpan? selectionOpt, CancellationToken cancellationToken)
85+
{
86+
if (selectionOpt.HasValue)
87+
{
88+
var ruleFactory = workspace.Services.GetRequiredService<IHostDependentFormattingRuleFactoryService>();
8389

84-
if (!changes.IsEmpty)
90+
changes = ruleFactory.FilterFormattedChanges(documentId, selectionOpt.Value, changes).ToList();
91+
if (changes.Count == 0)
8592
{
86-
using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
87-
{
88-
textBuffer.ApplyChanges(changes);
89-
}
93+
return;
9094
}
95+
}
9196

92-
transaction.Complete();
97+
using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
98+
{
99+
workspace.ApplyTextChanges(documentId, changes, cancellationToken);
93100
}
94101
}
95102

@@ -184,7 +191,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
184191
using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Automatic_Formatting))
185192
{
186193
transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;
187-
subjectBuffer.ApplyChanges(textChanges);
194+
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, textChanges, cancellationToken);
188195
transaction.Complete();
189196
}
190197

@@ -195,7 +202,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
195202
return;
196203
}
197204

198-
var snapshotAfterFormatting = subjectBuffer.CurrentSnapshot;
205+
var snapshotAfterFormatting = args.SubjectBuffer.CurrentSnapshot;
199206

200207
var oldCaretPosition = caretPosition.Value.TranslateTo(snapshotAfterFormatting, PointTrackingMode.Negative);
201208
var newCaretPosition = newCaretPositionMarker.Value.TranslateTo(snapshotAfterFormatting, PointTrackingMode.Negative);

src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs

+36-37
Original file line numberDiff line numberDiff line change
@@ -249,59 +249,58 @@ private AsyncCompletionData.CommitResult Commit(
249249
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None);
250250
}
251251

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

257256
// edit.Apply() may trigger changes made by extensions.
258257
// updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions.
259-
updatedCurrentSnapshot = edit.Apply();
260-
}
258+
var updatedCurrentSnapshot = edit.Apply();
261259

262-
if (change.NewPosition.HasValue)
263-
{
264-
// Roslyn knows how to position the caret in the snapshot we just created.
265-
// If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one.
266-
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value));
267-
}
268-
else
269-
{
270-
// Or, If we're doing a minimal change, then the edit that we make to the
271-
// buffer may not make the total text change that places the caret where we
272-
// would expect it to go based on the requested change. In this case,
273-
// determine where the item should go and set the care manually.
274-
275-
// Note: we only want to move the caret if the caret would have been moved
276-
// by the edit. i.e. if the caret was actually in the mapped span that
277-
// we're replacing.
278-
var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer);
279-
if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value))
260+
if (change.NewPosition.HasValue)
280261
{
281-
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0));
262+
// Roslyn knows how to position the caret in the snapshot we just created.
263+
// If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one.
264+
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value));
282265
}
283266
else
284267
{
285-
view.Caret.EnsureVisible();
268+
// Or, If we're doing a minimal change, then the edit that we make to the
269+
// buffer may not make the total text change that places the caret where we
270+
// would expect it to go based on the requested change. In this case,
271+
// determine where the item should go and set the care manually.
272+
273+
// Note: we only want to move the caret if the caret would have been moved
274+
// by the edit. i.e. if the caret was actually in the mapped span that
275+
// we're replacing.
276+
var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer);
277+
if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value))
278+
{
279+
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0));
280+
}
281+
else
282+
{
283+
view.Caret.EnsureVisible();
284+
}
286285
}
287-
}
288286

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

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

302-
// Note: C# always completes synchronously, TypeScript is async
303-
var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
304-
subjectBuffer.ApplyChanges(changes);
296+
if (currentDocument != null && formattingService != null)
297+
{
298+
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
299+
300+
// Note: C# always completes synchronously, TypeScript is async
301+
var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
302+
currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken);
303+
}
305304
}
306305
}
307306

src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs

-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Text;
8-
using Microsoft.CodeAnalysis.AddImport;
98
using Microsoft.CodeAnalysis.Diagnostics;
109
using Microsoft.CodeAnalysis.DocumentationComments;
1110
using Microsoft.CodeAnalysis.Formatting;
@@ -68,14 +67,6 @@ public static IndentationOptions GetIndentationOptions(this ITextBuffer textBuff
6867
};
6968
}
7069

71-
public static AddImportPlacementOptions GetAddImportPlacementOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, HostLanguageServices languageServices, bool allowInHiddenRegions)
72-
{
73-
var editorOptions = optionsProvider.Factory.GetOptions(textBuffer);
74-
var configOptions = new EditorAnalyzerConfigOptions(editorOptions);
75-
var fallbackOptions = optionsProvider.GlobalOptions.GetAddImportPlacementOptions(languageServices);
76-
return configOptions.GetAddImportPlacementOptions(allowInHiddenRegions, fallbackOptions, languageServices);
77-
}
78-
7970
public static IndentingStyle ToEditorIndentStyle(this FormattingOptions2.IndentStyle value)
8071
=> value switch
8172
{

src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs

-25
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.Collections.Generic;
65
using System.Diagnostics.CodeAnalysis;
76
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
87
using Microsoft.CodeAnalysis.Text;
9-
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
108
using Microsoft.VisualStudio.Text;
119

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

6260
return service != null;
6361
}
64-
65-
public static ITextSnapshot ApplyChange(this ITextBuffer buffer, TextChange change)
66-
{
67-
using var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null);
68-
edit.Replace(change.Span.ToSpan(), change.NewText);
69-
return edit.Apply();
70-
}
71-
72-
public static ITextSnapshot ApplyChanges(this ITextBuffer buffer, IEnumerable<TextChange> changes)
73-
{
74-
using var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null);
75-
return ApplyChanges(edit, changes);
76-
}
77-
78-
public static ITextSnapshot ApplyChanges(this ITextEdit edit, IEnumerable<TextChange> changes)
79-
{
80-
foreach (var change in changes)
81-
{
82-
edit.Replace(change.Span.ToSpan(), change.NewText);
83-
}
84-
85-
return edit.Apply();
86-
}
8762
}
8863
}

src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static void FormatAndApplyToBuffer(
4949

5050
using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken))
5151
{
52-
textBuffer.ApplyChanges(changes);
52+
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
5353
}
5454
}
5555

src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Imports Microsoft.VisualStudio.Text
2323
Imports Microsoft.VisualStudio.Text.Editor
2424
Imports Microsoft.VisualStudio.Text.Operations
2525
Imports Microsoft.VisualStudio.Text.Projection
26+
Imports Roslyn.Utilities
2627

2728
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
2829
<[UseExportProvider]>

0 commit comments

Comments
 (0)