From 002abc17c6fd95ba48cf091884cc056418cc2ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 7 Jul 2022 11:17:55 -0700 Subject: [PATCH] Apply changes directly to text buffer (#62337) --- .../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 17dc9571e37a0..a37c65bd2b14f 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs @@ -14,6 +14,7 @@ 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; @@ -99,18 +100,7 @@ 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); - 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(); + buffer.ApplyChanges(changes); } protected static async Task GetSmartTokenFormatterIndentationAsync( diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs index 67c86811f3174..71abcf9cfee86 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs @@ -125,49 +125,39 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom /// private void ApplyEdits(Document document, ITextView textView, ITextBuffer subjectBuffer, string title, CommentSelectionResult edits, CancellationToken cancellationToken) { - 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))); + var originalSnapshot = subjectBuffer.CurrentSnapshot; // Apply the text changes. - SourceText newText; using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService)) { - 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); - + subjectBuffer.ApplyChanges(edits.TextChanges); transaction.Complete(); } - // 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.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)); + 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 formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); + var newText = subjectBuffer.CurrentSnapshot.AsText(); + var oldSyntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken); + var newRoot = oldSyntaxTree.WithChangedText(newText).GetRoot(cancellationToken); - var updatedDocument = workspace.CurrentSolution.GetRequiredDocument(document.Id); - var root = updatedDocument.GetRequiredSyntaxRootSynchronously(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); - 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); + subjectBuffer.ApplyChanges(formattedChanges); - 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 4dea901aec400..830d43f52580b 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs @@ -55,7 +55,9 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos return; } - var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var subjectBuffer = args.SubjectBuffer; + + var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return; @@ -86,16 +88,16 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos } var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive); - var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan(); + var span = trackingSpan.GetSpan(subjectBuffer.CurrentSnapshot).Span.ToTextSpan(); // Note: C# always completes synchronously, TypeScript is async - var changes = formattingService.GetFormattingChangesOnPasteAsync(document, args.SubjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken); + var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.IsEmpty) { return; } - solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); + subjectBuffer.ApplyChanges(changes); } } } diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs index ce9b578b312a9..e2368ffe6aa08 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs @@ -75,28 +75,21 @@ private void Format(ITextView textView, ITextBuffer textBuffer, Document documen return; } - 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 (selectionOpt.HasValue) + { + var ruleFactory = document.Project.Solution.Workspace.Services.GetRequiredService(); + changes = ruleFactory.FilterFormattedChanges(document.Id, selectionOpt.Value, changes).ToImmutableArray(); + } - changes = ruleFactory.FilterFormattedChanges(documentId, selectionOpt.Value, changes).ToList(); - if (changes.Count == 0) + if (!changes.IsEmpty) { - return; + using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken)) + { + textBuffer.ApplyChanges(changes); + } } - } - using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken)) - { - workspace.ApplyTextChanges(documentId, changes, cancellationToken); + transaction.Complete(); } } @@ -191,7 +184,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Automatic_Formatting)) { transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; - document.Project.Solution.Workspace.ApplyTextChanges(document.Id, textChanges, cancellationToken); + subjectBuffer.ApplyChanges(textChanges); transaction.Complete(); } @@ -202,7 +195,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } - var snapshotAfterFormatting = args.SubjectBuffer.CurrentSnapshot; + var snapshotAfterFormatting = 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 3a811b0c009f3..efc87863fca98 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -249,58 +249,59 @@ 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. - var updatedCurrentSnapshot = edit.Apply(); + updatedCurrentSnapshot = edit.Apply(); + } - if (change.NewPosition.HasValue) + 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)) { - // 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)); + view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); } 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)) - { - view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); - } - else - { - view.Caret.EnsureVisible(); - } + view.Caret.EnsureVisible(); } + } - includesCommitCharacter = change.IncludesCommitCharacter; + 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(); + 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(); - if (currentDocument != null && formattingService != null) - { - var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + 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); - } + // Note: C# always completes synchronously, TypeScript is async + var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + subjectBuffer.ApplyChanges(changes); } } diff --git a/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs index 488c08f4dbf6a..8ca8529369c9d 100644 --- a/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs +++ b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs @@ -5,6 +5,7 @@ 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; @@ -67,6 +68,14 @@ 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 f450c5f63ccce..5ca95f3c0215d 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextBufferExtensions.cs @@ -2,9 +2,11 @@ // 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 @@ -59,5 +61,28 @@ 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 02efb817ab7af..f44c5f33b5999 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)) { - document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); + textBuffer.ApplyChanges(changes); } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 189654c326a60..955c56b89f99f 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -23,7 +23,6 @@ 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 40bde45965f3a..547f1e94eb88f 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/SpitLinesResult.vb @@ -49,8 +49,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration ' in the buffer Dim joinedLines = If(_startOnCurrentLine, "", bufferNewLine) + String.Join(bufferNewLine, _lines) - document.Project.Solution.Workspace.ApplyTextChanges( - document.Id, SpecializedCollections.SingletonEnumerable(New TextChange(New TextSpan(caretPosition, 0), joinedLines)), CancellationToken.None) + subjectBuffer.ApplyChange(New TextChange(New TextSpan(caretPosition, 0), joinedLines)) SetIndentForFirstBlankLine(textView, subjectBuffer, smartIndentationService, currentLine) End Sub diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb index c4ef6e59106d3..eb290da618bfd 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb @@ -323,22 +323,20 @@ 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, - cancellationToken As CancellationToken + endText As String ) As Boolean - Dim document = view.TextSnapshot.GetOpenDocumentInCurrentContextWithChanges() + Dim document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges() If document Is Nothing Then Return False End If - document.Project.Solution.Workspace.ApplyTextChanges( - document.Id, SpecializedCollections.SingletonEnumerable( - New TextChange(New TextSpan(insertPosition, 0), endText)), cancellationToken) + subjectBuffer.ApplyChange(New TextChange(New TextSpan(insertPosition, 0), endText)) - Dim caretPosAfterEdit = New SnapshotPoint(view.TextSnapshot, caretPosition) + Dim caretPosAfterEdit = New SnapshotPoint(subjectBuffer.CurrentSnapshot, caretPosition) view.TryMoveCaretToAndEnsureVisible(caretPosAfterEdit) @@ -369,7 +367,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "]]>" - Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) + Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) End Using End Function @@ -397,7 +395,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "-->" - Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) + Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) End Using End Function @@ -425,7 +423,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endTagText = "" - Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endTagText, cancellationToken) + Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endTagText) End Using End Function @@ -449,7 +447,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, state.CaretPosition, state.TokenToLeft.Span.End + 1, endText, cancellationToken) + Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End + 1, endText) End Using End Function @@ -478,7 +476,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration End If Dim endText = "?>" - Return InsertEndTextAndUpdateCaretPosition(textView, state.CaretPosition, state.TokenToLeft.Span.End, endText, cancellationToken) + Return InsertEndTextAndUpdateCaretPosition(textView, subjectBuffer, state.CaretPosition, state.TokenToLeft.Span.End, endText) End Using End Function diff --git a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs index 6879baa522757..592fd104d17eb 100644 --- a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetExpansionClient.cs @@ -287,17 +287,8 @@ private void CleanUpEndLocation(ITrackingSpan? endTrackingSpan) { _indentCaretOnCommit = true; - 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); - } + var formattingOptions = SubjectBuffer.GetLineFormattingOptions(EditorOptionsService, explicitFormat: false); + _indentDepth = lineText.GetColumnFromLineOffset(lineText.Length, formattingOptions.TabSize); SubjectBuffer.Delete(new Span(line.Start.Position, line.Length)); _ = SubjectBuffer.CurrentSnapshot.GetSpan(new Span(line.Start.Position, 0)); @@ -1066,14 +1057,15 @@ private void AddReferencesAndImports( return; } - var documentWithImports = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var documentWithImports = SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (documentWithImports == null) { return; } - var addImportOptions = documentWithImports.GetAddImportPlacementOptionsAsync(EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - var formattingOptions = documentWithImports.GetSyntaxFormattingOptionsAsync(EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var languageServices = documentWithImports.Project.LanguageServices; + var addImportOptions = SubjectBuffer.GetAddImportPlacementOptions(EditorOptionsService, languageServices, documentWithImports.AllowImportsInHiddenRegions()); + var formattingOptions = SubjectBuffer.GetSyntaxFormattingOptions(EditorOptionsService, languageServices, explicitFormat: false); 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 780eafe7352d9..b3768be8563be 100644 --- a/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb +++ b/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb @@ -310,22 +310,24 @@ 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) - 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))) + editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, False) + editorOptions.SetOptionValue(DefaultOptions.TabSizeOptionId, tabSize) + editorOptions.SetOptionValue(DefaultOptions.IndentSizeOptionId, tabSize) Dim snippetExpansionClient = New SnippetExpansionClient( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), Guids.CSharpLanguageServiceId, document.GetTextView(), - document.GetTextBuffer(), + textBuffer, signatureHelpControllerProvider:=Nothing, editorCommandHandlerServiceFactory:=Nothing, editorAdaptersFactoryService:=Nothing, workspace.ExportProvider.GetExports(Of ArgumentProvider, OrderableLanguageMetadata)().ToImmutableArray(), - workspace.GetService(Of EditorOptionsService)()) + editorOptionsService) SnippetExpansionClientTestsHelper.TestFormattingAndCaretPosition(snippetExpansionClient, document, expectedResult, tabSize * 3) End Using