Skip to content

Commit af73a4b

Browse files
committed
Revert "Revert "Apply changes directly to text buffer (dotnet#62337)" (dotnet#62589)"
This reverts commit 0a39dcf.
1 parent 0a39dcf commit af73a4b

File tree

13 files changed

+135
-135
lines changed

13 files changed

+135
-135
lines changed

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

+2-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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;
1718
using Microsoft.CodeAnalysis.Editor.UnitTests;
1819
using Microsoft.CodeAnalysis.Editor.UnitTests.Formatting;
1920
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
@@ -99,18 +100,7 @@ private static async Task TokenFormatWorkerAsync(TestWorkspace workspace, ITextB
99100
var formatter = new CSharpSmartTokenFormatter(options, rules, (CompilationUnitSyntax)documentSyntax.Root, documentSyntax.Text);
100101
var changes = formatter.FormatToken(token, CancellationToken.None);
101102

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();
103+
buffer.ApplyChanges(changes);
114104
}
115105

116106
protected static async Task<int> GetSmartTokenFormatterIndentationAsync(

src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs

+17-27
Original file line numberDiff line numberDiff line change
@@ -125,49 +125,39 @@ 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 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)));
128+
var originalSnapshot = subjectBuffer.CurrentSnapshot;
134129

135130
// Apply the text changes.
136-
SourceText newText;
137131
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
138132
{
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-
133+
subjectBuffer.ApplyChanges(edits.TextChanges);
148134
transaction.Complete();
149135
}
150136

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));
153-
154-
if (trackingSnapshotSpans.Any())
137+
if (edits.TrackingSpans.Any())
155138
{
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));
145+
156146
if (edits.ResultOperation == Operation.Uncomment && document.SupportsSyntaxTree)
157147
{
158148
// Format the document only during uncomment operations. Use second transaction so it can be undone.
159149
using var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService);
160150

161-
var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false);
151+
var newText = subjectBuffer.CurrentSnapshot.AsText();
152+
var oldSyntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken);
153+
var newRoot = oldSyntaxTree.WithChangedText(newText).GetRoot(cancellationToken);
162154

163-
var updatedDocument = workspace.CurrentSolution.GetRequiredDocument(document.Id);
164-
var root = updatedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken);
155+
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);
165158

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);
159+
subjectBuffer.ApplyChanges(formattedChanges);
169160

170-
workspace.ApplyDocumentChanges(formattedDocument, cancellationToken);
171161
transaction.Complete();
172162
}
173163

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

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

58-
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
58+
var subjectBuffer = args.SubjectBuffer;
59+
60+
var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
5961
if (document == null)
6062
{
6163
return;
@@ -86,16 +88,16 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos
8688
}
8789

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

9193
// Note: C# always completes synchronously, TypeScript is async
92-
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, args.SubjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
94+
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
9395
if (changes.IsEmpty)
9496
{
9597
return;
9698
}
9799

98-
solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
100+
subjectBuffer.ApplyChanges(changes);
99101
}
100102
}
101103
}

src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs

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

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>();
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+
}
8983

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

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

@@ -191,7 +184,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
191184
using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Automatic_Formatting))
192185
{
193186
transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;
194-
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, textChanges, cancellationToken);
187+
subjectBuffer.ApplyChanges(textChanges);
195188
transaction.Complete();
196189
}
197190

@@ -202,7 +195,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
202195
return;
203196
}
204197

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

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

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

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

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

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

260-
if (change.NewPosition.HasValue)
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))
261280
{
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));
281+
view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0));
265282
}
266283
else
267284
{
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-
}
285+
view.Caret.EnsureVisible();
285286
}
287+
}
286288

287-
includesCommitCharacter = change.IncludesCommitCharacter;
289+
includesCommitCharacter = change.IncludesCommitCharacter;
288290

289-
if (roslynItem.Rules.FormatOnCommit)
290-
{
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>();
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>();
295297

296-
if (currentDocument != null && formattingService != null)
297-
{
298-
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
298+
if (currentDocument != null && formattingService != null)
299+
{
300+
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
299301

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-
}
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);
304305
}
305306
}
306307

src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Text;
8+
using Microsoft.CodeAnalysis.AddImport;
89
using Microsoft.CodeAnalysis.Diagnostics;
910
using Microsoft.CodeAnalysis.DocumentationComments;
1011
using Microsoft.CodeAnalysis.Formatting;
@@ -67,6 +68,14 @@ public static IndentationOptions GetIndentationOptions(this ITextBuffer textBuff
6768
};
6869
}
6970

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+
7079
public static IndentingStyle ToEditorIndentStyle(this FormattingOptions2.IndentStyle value)
7180
=> value switch
7281
{

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

+25
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
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;
56
using System.Diagnostics.CodeAnalysis;
67
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
78
using Microsoft.CodeAnalysis.Text;
9+
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
810
using Microsoft.VisualStudio.Text;
911

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

6062
return service != null;
6163
}
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+
}
6287
}
6388
}

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-
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken);
52+
textBuffer.ApplyChanges(changes);
5353
}
5454
}
5555

0 commit comments

Comments
 (0)