From 0a39dcfab4c75d7ab4b03146db91c03a1f309fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 13 Jul 2022 05:11:40 -0700 Subject: [PATCH] Revert "Apply changes directly to text buffer (#62337)" (#62589) This reverts commit 002abc17c6fd95ba48cf091884cc056418cc2ca7. Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1568401 --- .../Indentation/CSharpFormatterTestsBase.cs | 14 +++- .../AbstractCommentSelectionBase.cs | 44 ++++++----- .../Formatting/FormatCommandHandler.Paste.cs | 10 +-- .../Core/Formatting/FormatCommandHandler.cs | 33 +++++---- .../AsyncCompletion/CommitManager.cs | 73 +++++++++---------- .../Core/Options/TextBufferOptionProviders.cs | 9 --- .../Extensions/ITextBufferExtensions.cs | 25 ------- .../Extensions/ITextSnapshotExtensions.cs | 2 +- .../CSharpCompletionCommandHandlerTests.vb | 1 + .../EndConstructGeneration/SpitLinesResult.vb | 3 +- ...isualBasicEndConstructGenerationService.vb | 22 +++--- .../AbstractSnippetExpansionClient.cs | 20 +++-- .../CSharpSnippetExpansionClientTests.vb | 14 ++-- 13 files changed, 135 insertions(+), 135 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs index a37c65bd2b14f..17dc9571e37a0 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs @@ -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; @@ -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 changes) + { + using var edit = buffer.CreateEdit(); + foreach (var change in changes) + { + edit.Replace(change.Span.ToSpan(), change.NewText); + } + + edit.Apply(); } protected static async Task GetSmartTokenFormatterIndentationAsync( diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs index 71abcf9cfee86..67c86811f3174 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs @@ -125,39 +125,49 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom /// 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(); } diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs index 830d43f52580b..4dea901aec400 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs @@ -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; @@ -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); } } } diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs index e2368ffe6aa08..ce9b578b312a9 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs @@ -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(); - 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 changes, TextSpan? selectionOpt, CancellationToken cancellationToken) + { + if (selectionOpt.HasValue) + { + var ruleFactory = workspace.Services.GetRequiredService(); - 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); } } @@ -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(); } @@ -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); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index efc87863fca98..3a811b0c009f3 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -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(); + 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(); - // 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); + } } } diff --git a/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs index 8ca8529369c9d..488c08f4dbf6a 100644 --- a/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs +++ b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs @@ -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; @@ -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 { diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs index 5ca95f3c0215d..f450c5f63ccce 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs @@ -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 @@ -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 changes) - { - using var edit = buffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null); - return ApplyChanges(edit, changes); - } - - public static ITextSnapshot ApplyChanges(this ITextEdit edit, IEnumerable changes) - { - foreach (var change in changes) - { - edit.Replace(change.Span.ToSpan(), change.NewText); - } - - return edit.Apply(); - } } } diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs index f44c5f33b5999..02efb817ab7af 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs @@ -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); } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 955c56b89f99f..189654c326a60 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -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]> diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb index 547f1e94eb88f..40bde45965f3a 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb @@ -49,7 +49,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration ' in the buffer Dim joinedLines = If(_startOnCurrentLine, "", bufferNewLine) + String.Join(bufferNewLine, _lines) - subjectBuffer.ApplyChange(New TextChange(New TextSpan(caretPosition, 0), joinedLines)) + document.Project.Solution.Workspace.ApplyTextChanges( + document.Id, SpecializedCollections.SingletonEnumerable(New TextChange(New TextSpan(caretPosition, 0), joinedLines)), CancellationToken.None) SetIndentForFirstBlankLine(textView, subjectBuffer, smartIndentationService, currentLine) End Sub diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb index eb290da618bfd..c4ef6e59106d3 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb @@ -323,20 +323,22 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration Private Shared Function InsertEndTextAndUpdateCaretPosition( view As ITextView, - subjectBuffer As ITextBuffer, insertPosition As Integer, caretPosition As Integer, - endText As String + endText As String, + cancellationToken As CancellationToken ) As Boolean - Dim document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges() + Dim document = view.TextSnapshot.GetOpenDocumentInCurrentContextWithChanges() If document Is Nothing Then Return False End If - subjectBuffer.ApplyChange(New TextChange(New TextSpan(insertPosition, 0), endText)) + document.Project.Solution.Workspace.ApplyTextChanges( + document.Id, SpecializedCollections.SingletonEnumerable( + New TextChange(New TextSpan(insertPosition, 0), endText)), cancellationToken) - Dim caretPosAfterEdit = New SnapshotPoint(subjectBuffer.CurrentSnapshot, caretPosition) + Dim caretPosAfterEdit = New SnapshotPoint(view.TextSnapshot, caretPosition) view.TryMoveCaretToAndEnsureVisible(caretPosAfterEdit) @@ -367,7 +369,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "]]>" - Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) + Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) End Using End Function @@ -395,7 +397,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "-->" - Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) + Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) End Using End Function @@ -423,7 +425,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endTagText = "" - Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endTagText) + Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endTagText, cancellationToken) End Using End Function @@ -447,7 +449,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = " %>" ' NOTE: two spaces are inserted. The caret will be moved between them - Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End + 1, endText) + Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End + 1, endText, cancellationToken) End Using End Function @@ -476,7 +478,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "?>" - Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) + Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) End Using End Function diff --git a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs index 592fd104d17eb..6879baa522757 100644 --- a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs @@ -287,8 +287,17 @@ private void CleanUpEndLocation(ITrackingSpan? endTrackingSpan) { _indentCaretOnCommit = true; - var formattingOptions = SubjectBuffer.GetLineFormattingOptions(EditorOptionsService, explicitFormat: false); - _indentDepth = lineText.GetColumnFromLineOffset(lineText.Length, formattingOptions.TabSize); + var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document != null) + { + var formattingOptions = document.GetLineFormattingOptionsAsync(EditorOptionsService.GlobalOptions, CancellationToken.None).AsTask().WaitAndGetResult(CancellationToken.None); + _indentDepth = lineText.GetColumnFromLineOffset(lineText.Length, formattingOptions.TabSize); + } + else + { + // If we don't have a document, then just guess the typical default TabSize value. + _indentDepth = lineText.GetColumnFromLineOffset(lineText.Length, tabSize: 4); + } SubjectBuffer.Delete(new Span(line.Start.Position, line.Length)); _ = SubjectBuffer.CurrentSnapshot.GetSpan(new Span(line.Start.Position, 0)); @@ -1057,15 +1066,14 @@ private void AddReferencesAndImports( return; } - var documentWithImports = SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var documentWithImports = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (documentWithImports == null) { return; } - var languageServices = documentWithImports.Project.LanguageServices; - var addImportOptions = SubjectBuffer.GetAddImportPlacementOptions(EditorOptionsService, languageServices, documentWithImports.AllowImportsInHiddenRegions()); - var formattingOptions = SubjectBuffer.GetSyntaxFormattingOptions(EditorOptionsService, languageServices, explicitFormat: false); + var addImportOptions = documentWithImports.GetAddImportPlacementOptionsAsync(EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var formattingOptions = documentWithImports.GetSyntaxFormattingOptionsAsync(EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); documentWithImports = AddImports(documentWithImports, addImportOptions, formattingOptions, position, snippetNode, cancellationToken); AddReferences(documentWithImports.Project, snippetNode); diff --git a/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb b/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb index b3768be8563be..780eafe7352d9 100644 --- a/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb +++ b/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb @@ -310,24 +310,22 @@ using G= H.I; Using workspace = TestWorkspace.Create(workspaceXml, openDocuments:=False) Dim document = workspace.Documents.Single() - Dim textBuffer = document.GetTextBuffer() - Dim editorOptionsService = workspace.GetService(Of EditorOptionsService)() - Dim editorOptions = editorOptionsService.Factory.GetOptions(textBuffer) - editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, False) - editorOptions.SetOptionValue(DefaultOptions.TabSizeOptionId, tabSize) - editorOptions.SetOptionValue(DefaultOptions.IndentSizeOptionId, tabSize) + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options _ + .WithChangedOption(FormattingOptions.UseTabs, document.Project.Language, True) _ + .WithChangedOption(FormattingOptions.TabSize, document.Project.Language, tabSize) _ + .WithChangedOption(FormattingOptions.IndentationSize, document.Project.Language, tabSize))) Dim snippetExpansionClient = New SnippetExpansionClient( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), Guids.CSharpLanguageServiceId, document.GetTextView(), - textBuffer, + document.GetTextBuffer(), signatureHelpControllerProvider:=Nothing, editorCommandHandlerServiceFactory:=Nothing, editorAdaptersFactoryService:=Nothing, workspace.ExportProvider.GetExports(Of ArgumentProvider, OrderableLanguageMetadata)().ToImmutableArray(), - editorOptionsService) + workspace.GetService(Of EditorOptionsService)()) SnippetExpansionClientTestsHelper.TestFormattingAndCaretPosition(snippetExpansionClient, document, expectedResult, tabSize * 3) End Using