From 7cee927fab7aba78a1437c42acb77db71e2a04f6 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 22 Feb 2025 16:37:10 -0800 Subject: [PATCH] Change override completion to select text after updating the buffer. (#76983) * Change override completion to select text after updating the buffer. This (along with https://github.com/dotnet/roslyn/pull/76969) will allow a user to commit an override completion and easily replace the default text (and still use the expression body fixers). This required the CompletionChange class to now allow the data to represent a selection instead of just a cursor position. --- ...tInterfaceMemberCompletionProviderTests.cs | 6 +-- .../OverrideCompletionProviderTests.cs | 30 +++++++------- ...eCompletionProviderTests_ExpressionBody.cs | 6 +-- .../PartialMethodCompletionProviderTests.cs | 32 +++++++-------- .../AsyncCompletion/CommitManager.cs | 8 ++-- .../Shared/Extensions/ITextViewExtensions.cs | 9 +++++ .../AbstractCompletionProviderTests.cs | 10 ++++- .../CompletionUtilities.cs | 39 +++++++++++++++---- ...plicitInterfaceMemberCompletionProvider.cs | 4 +- .../OverrideCompletionProvider.cs | 4 +- .../PartialMethodCompletionProvider.cs | 4 +- .../Portable/Completion/CompletionChange.cs | 26 ++++++++++++- ...stractMemberInsertingCompletionProvider.cs | 25 ++++++------ .../OverrideCompletionProvider.vb | 10 ++--- 14 files changed, 137 insertions(+), 76 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProviderTests.cs index da1717ad8e0d8..f8a06a905dd66 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProviderTests.cs @@ -169,7 +169,7 @@ class Bar : IGoo { void IGoo.Goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -549,7 +549,7 @@ class Bar : IGoo { int IGoo.Generic(K key, V value) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -1214,7 +1214,7 @@ class C : IFoo { static bool IFoo.TryDecode(out DecodeError? decodeError, out string? errorMessage) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index 488b059558925..4c5e053f27347 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -1878,7 +1878,7 @@ public override int goo { get { - return base.goo;$$ + [|return base.goo;|] } set @@ -1921,7 +1921,7 @@ public override int goo { get { - return base.goo;$$ + [|return base.goo;|] } set @@ -1963,7 +1963,7 @@ public override int goo { get { - return base.goo;$$ + [|return base.goo;|] } set @@ -2005,7 +2005,7 @@ public override int goo { set { - base.goo = value;$$ + [|base.goo = value;|] } } } @@ -2041,7 +2041,7 @@ public override int goo { get { - return base.goo;$$ + [|return base.goo;|] } } } @@ -2102,7 +2102,7 @@ public override int this[[MyPublic] int i] { get { - return base[i];$$ + [|return base[i];|] } set @@ -2392,7 +2392,7 @@ public override T this[int i] { get { - return base[i];$$ + [|return base[i];|] } set @@ -2435,7 +2435,7 @@ public override T this[int i] { get { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } set @@ -2624,7 +2624,7 @@ public override int @class { get { - return base.@class;$$ + [|return base.@class;|] } set @@ -2939,7 +2939,7 @@ public override int Goo { get { - return base.Goo;$$ + [|return base.Goo;|] } } } @@ -3594,7 +3594,7 @@ public override required int Prop { get { - return base.Prop;$$ + [|return base.Prop;|] } } } @@ -3635,7 +3635,7 @@ public override required int Prop { get { - return base.Prop;$$ + [|return base.Prop;|] } } } @@ -3676,7 +3676,7 @@ public override required int Prop { get { - return base.Prop;$$ + [|return base.Prop;|] } } } @@ -3743,7 +3743,7 @@ public override int this[int i] { get { - return base[i];$$ + [|return base[i];|] } set @@ -3885,7 +3885,7 @@ public override int goo { get { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } set diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs index 9be11ad9073a2..44b6bea63e94b 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs @@ -51,7 +51,7 @@ class B public virtual int A { get; set; } class C : B { - public override int A { get => base.A$$; set => base.A = value; } + public override int A { get => [|base.A|]; set => base.A = value; } } } """; @@ -79,7 +79,7 @@ class B public virtual int A { get; } class C : B { - public override int A => base.A;$$ + public override int A => [|base.A|]; } } """; @@ -107,7 +107,7 @@ class B public virtual int A() => 2; class C : B { - public override int A() => base.A();$$ + public override int A() => [|base.A()|]; } } """; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs index ad53c33da24ba..6b09c636c744f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs @@ -417,7 +417,7 @@ partial class c partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -444,7 +444,7 @@ partial class c public partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -471,7 +471,7 @@ partial class c partial void goo(T bar) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -498,7 +498,7 @@ partial class c public partial void goo(T bar) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -525,7 +525,7 @@ partial class c partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -552,7 +552,7 @@ partial class c private partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -585,7 +585,7 @@ partial class c { partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -618,7 +618,7 @@ partial class c { public partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -645,7 +645,7 @@ partial struct c partial void goo() { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } """; @@ -744,7 +744,7 @@ partial class Bar async partial void Goo() { - throw new NotImplementedException();$$ + [|throw new NotImplementedException();|] } } """; @@ -775,7 +775,7 @@ partial class Bar public async partial void Goo() { - throw new NotImplementedException();$$ + [|throw new NotImplementedException();|] } } """; @@ -806,7 +806,7 @@ partial class Bar partial void Goo() { - throw new NotImplementedException();$$ + [|throw new NotImplementedException();|] } } """; @@ -838,7 +838,7 @@ partial class PClass partial void PMethod(int i) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } } @@ -870,7 +870,7 @@ partial class PClass public partial void PMethod(int i) { - throw new System.NotImplementedException();$$ + [|throw new System.NotImplementedException();|] } } } @@ -903,7 +903,7 @@ partial class Bar partial class Bar { partial void Foo(); - partial void Foo() => throw new NotImplementedException();$$ + partial void Foo() => [|throw new NotImplementedException()|]; } """; @@ -936,7 +936,7 @@ partial class Bar partial class Bar { public partial void Foo(); - public partial void Foo() => throw new NotImplementedException();$$ + public partial void Foo() => [|throw new NotImplementedException()|]; } """ ; diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 7d4b4c67a47bc..cd0abaf590d4b 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -278,11 +278,11 @@ private AsyncCompletionData.CommitResult Commit( updatedCurrentSnapshot = edit.Apply(); } - if (change.NewPosition.HasValue) + if (change.NewSelection.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)); + // Roslyn knows how to set the selection in the snapshot we just created. + // If there were more edits made by extensions, TrySetSelectionAndEnsureVisible maps the snapshot point to the most recent one. + view.TrySetSelectionAndEnsureVisible(new SnapshotSpan(updatedCurrentSnapshot, change.NewSelection.Value.ToSpan())); } else { diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs index 8c5027115121d..b328be35f519c 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs @@ -108,6 +108,15 @@ public static void SetMultiSelection(this ITextView textView, IEnumerable textView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(point), outliningManagerService, ensureSpanVisibleOptions); diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 55e0869431cca..b4808de89e235 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -636,7 +637,7 @@ private async Task VerifyCustomCommitWorkerAsync( { using var workspaceFixture = GetOrCreateWorkspaceFixture(); - MarkupTestFile.GetPosition(expectedCodeAfterCommit, out var actualExpectedCode, out int expectedCaretPosition); + MarkupTestFile.GetPositionAndSpan(expectedCodeAfterCommit, out var actualExpectedCode, out var expectedCaretPosition, out TextSpan? expectedSelectionSpan); var options = GetCompletionOptions(); @@ -661,10 +662,15 @@ private async Task VerifyCustomCommitWorkerAsync( var textBuffer = workspaceFixture.Target.CurrentDocument.GetTextBuffer(); var actualCodeAfterCommit = textBuffer.CurrentSnapshot.AsText().ToString(); + var selectionSpan = commit.NewSelection ?? textView.Selection.StreamSelectionSpan.SnapshotSpan.Span.ToTextSpan(); var caretPosition = commit.NewPosition ?? textView.Caret.Position.BufferPosition.Position; AssertEx.EqualOrDiff(actualExpectedCode, actualCodeAfterCommit); - Assert.Equal(expectedCaretPosition, caretPosition); + + if (expectedSelectionSpan != null) + Assert.Equal(expectedSelectionSpan, selectionSpan); + else if (expectedCaretPosition != null) + Assert.Equal(expectedCaretPosition, caretPosition); } private void VerifyCustomCommitWorker( diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs index 2e61efc068f70..8ee8d2310fdef 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs @@ -188,30 +188,53 @@ public static SyntaxNode GetTargetCaretPositionForMethod(BaseMethodDeclarationSy } } - public static SyntaxNode GetTargetCaretNodeForInsertedMember(SyntaxNode caretTarget) + public static TextSpan GetTargetSelectionSpanForMethod(BaseMethodDeclarationSyntax methodDeclaration) + { + if (methodDeclaration.ExpressionBody is not null) + { + // select the expression span + return methodDeclaration.ExpressionBody.Expression.Span; + } + else if (methodDeclaration.Body is not null) + { + // select the last statement in the method + return methodDeclaration.Body.Statements.Last().Span; + } + else + { + return methodDeclaration.Span; + } + } + + public static TextSpan GetTargetSelectionSpanForInsertedMember(SyntaxNode caretTarget) { switch (caretTarget) { case EventFieldDeclarationSyntax: - // Inserted Event declarations are a single line, so move to the end of the line. - return caretTarget; + // Inserted Event declarations are a single line, so move caret to the end of the line. + return new TextSpan(caretTarget.Span.End, 0); case BaseMethodDeclarationSyntax methodDeclaration: - return GetTargetCaretPositionForMethod(methodDeclaration); + return GetTargetSelectionSpanForMethod(methodDeclaration); case BasePropertyDeclarationSyntax propertyDeclaration: { - // property: no accessors; move to the end of the declaration if (propertyDeclaration.AccessorList is { Accessors: [var firstAccessor, ..] }) { - // move to the end of the last statement of the first accessor + // select the last statement of the first accessor var firstAccessorStatement = (SyntaxNode)firstAccessor.Body?.Statements.LastOrDefault() ?? firstAccessor.ExpressionBody!.Expression; - return firstAccessorStatement; + return firstAccessorStatement.Span; + } + else if (propertyDeclaration is PropertyDeclarationSyntax propertyDeclarationSyntax && propertyDeclarationSyntax.ExpressionBody.Expression is ExpressionSyntax expression) + { + // expression-bodied property: select the expression + return expression.Span; } else { - return propertyDeclaration; + // property: no accessors; move caret to the end of the declaration + return new TextSpan(propertyDeclaration.Span.End, 0); } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs index 9312dc1603a1a..5deb7c2ba7353 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs @@ -97,9 +97,9 @@ protected override SyntaxNode GetSyntax(SyntaxToken token) throw ExceptionUtilities.UnexpectedValue(token); } - protected override int GetTargetCaretPosition(SyntaxNode caretTarget) + protected override TextSpan GetTargetSelectionSpan(SyntaxNode caretTarget) { - return CompletionUtilities.GetTargetCaretNodeForInsertedMember(caretTarget).GetLocation().SourceSpan.End; + return CompletionUtilities.GetTargetSelectionSpanForInsertedMember(caretTarget); } public override async Task ProvideCompletionsAsync(CompletionContext context) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs index 0dfefd0d30513..46c287e34f3dd 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs @@ -204,8 +204,8 @@ public override ImmutableArray FilterOverrides(ImmutableArray return filteredMembers.Length > 0 ? filteredMembers : members; } - protected override int GetTargetCaretPosition(SyntaxNode caretTarget) + protected override TextSpan GetTargetSelectionSpan(SyntaxNode caretTarget) { - return CompletionUtilities.GetTargetCaretNodeForInsertedMember(caretTarget).GetLocation().SourceSpan.End; + return CompletionUtilities.GetTargetSelectionSpanForInsertedMember(caretTarget); } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialMethodCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialMethodCompletionProvider.cs index 65f39e620f798..f1c6f787a9098 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialMethodCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialMethodCompletionProvider.cs @@ -61,10 +61,10 @@ protected override SyntaxNode GetSyntax(SyntaxToken token) ?? throw ExceptionUtilities.UnexpectedValue(token); } - protected override int GetTargetCaretPosition(SyntaxNode caretTarget) + protected override TextSpan GetTargetSelectionSpan(SyntaxNode caretTarget) { var methodDeclaration = (MethodDeclarationSyntax)caretTarget; - return CompletionUtilities.GetTargetCaretPositionForMethod(methodDeclaration).GetLocation().SourceSpan.End; + return CompletionUtilities.GetTargetSelectionSpanForInsertedMember(methodDeclaration); } protected override SyntaxToken GetToken(CompletionItem completionItem, SyntaxTree tree, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 34c86ae91789c..489acd9982f58 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -31,7 +31,13 @@ public sealed class CompletionChange /// The new caret position after the change has been applied. /// If null then the new caret position will be determined by the completion host. /// - public int? NewPosition { get; } + public int? NewPosition { get => NewSelection?.End; } + + /// + /// The new selection after the change has been applied. + /// If null then the new caret position will be determined by the completion host. + /// + internal TextSpan? NewSelection { get; } /// /// True if the changes include the typed character that caused the @@ -50,9 +56,15 @@ private CompletionChange( private CompletionChange( TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, ImmutableDictionary properties) + : this(textChange, textChanges, newPosition != null ? new TextSpan(newPosition.Value, 0) : null, includesCommitCharacter, properties) + { + } + + private CompletionChange( + TextChange textChange, ImmutableArray textChanges, TextSpan? newSelection, bool includesCommitCharacter, ImmutableDictionary properties) { TextChange = textChange; - NewPosition = newPosition; + NewSelection = newSelection; IncludesCommitCharacter = includesCommitCharacter; TextChanges = textChanges.NullToEmpty(); if (TextChanges.IsEmpty) @@ -106,6 +118,16 @@ public static CompletionChange Create( return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter); } + internal static CompletionChange Create( + TextChange textChange, + ImmutableArray textChanges, + ImmutableDictionary properties, + TextSpan? newSpan = null, + bool includesCommitCharacter = false) + { + return new CompletionChange(textChange, textChanges, newSpan, includesCommitCharacter, properties); + } + internal static CompletionChange Create( TextChange textChange, ImmutableArray textChanges, diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs index 85ecf6aa8d684..7760cc453e3b2 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs @@ -38,7 +38,8 @@ internal abstract partial class AbstractMemberInsertingCompletionProvider : LSPC protected abstract Task GenerateMemberAsync( Document document, CompletionItem item, Compilation compilation, ISymbol member, INamedTypeSymbol containingType, CancellationToken cancellationToken); - protected abstract int GetTargetCaretPosition(SyntaxNode caretTarget); + protected abstract TextSpan GetTargetSelectionSpan(SyntaxNode caretTarget); + protected abstract SyntaxNode GetSyntax(SyntaxToken commonSyntaxToken); protected static CompletionItemRules GetRules() @@ -46,17 +47,17 @@ protected static CompletionItemRules GetRules() public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) { - var (newDocument, newPosition) = await DetermineNewDocumentAsync(document, item, cancellationToken).ConfigureAwait(false); + var (newDocument, newSpan) = await DetermineNewDocumentAsync(document, item, cancellationToken).ConfigureAwait(false); var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var changesArray = changes.ToImmutableArray(); var change = Utilities.Collapse(newText, changesArray); - return CompletionChange.Create(change, changesArray, newPosition, includesCommitCharacter: true); + return CompletionChange.Create(change, changesArray, properties: ImmutableDictionary.Empty, newSpan, includesCommitCharacter: true); } - private async Task<(Document, int? caretPosition)> DetermineNewDocumentAsync( + private async Task<(Document, TextSpan? caretPosition)> DetermineNewDocumentAsync( Document document, CompletionItem completionItem, CancellationToken cancellationToken) @@ -168,7 +169,7 @@ private TextSpan ComputeDestinationSpan(SyntaxNode insertionRoot) return TextSpan.FromBounds(startToken.Value.SpanStart, line.EndIncludingLineBreak); } - private async Task<(Document Document, int? CaretPosition)> RemoveDestinationNodeAsync( + private async Task<(Document Document, TextSpan? Selection)> RemoveDestinationNodeAsync( Document memberContainingDocument, CodeCleanupOptions cleanupOptions, CancellationToken cancellationToken) { // We now have a replacement node inserted into the document, but we still have the source code that triggered completion (with associated trivia). @@ -228,18 +229,18 @@ private TextSpan ComputeDestinationSpan(SyntaxNode insertionRoot) var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // We have basically the final tree. Calculate the new caret position while we still have the annotations. - int? caretPosition = null; + TextSpan? newSpan = null; var caretTarget = root.GetAnnotatedNodes(_annotation).FirstOrDefault(); if (caretTarget != null) { - var targetPosition = GetTargetCaretPosition(caretTarget); + var targetSelectionSpan = GetTargetSelectionSpan(caretTarget); - if (targetPosition > 0 && targetPosition <= text.Length) + if (targetSelectionSpan.Start > 0 && targetSelectionSpan.End <= text.Length) { // The new replacement method should always be inserted before the destination span we're removing. - // This means the caret position in the inserted method should be safe to return as-is. - Debug.Assert(targetPosition < destinationSpan.Start); - caretPosition = targetPosition; + // This means the end selection position in the inserted method should be safe to return as-is. + Debug.Assert(targetSelectionSpan.End < destinationSpan.Start); + newSpan = targetSelectionSpan; } } @@ -249,7 +250,7 @@ private TextSpan ComputeDestinationSpan(SyntaxNode insertionRoot) var textChange = new TextChange(text.Lines[lineNumber].SpanIncludingLineBreak, string.Empty); text = text.WithChanges(textChange); - return (document.WithText(text), caretPosition); + return (document.WithText(text), newSpan); } internal override Task GetDescriptionWorkerAsync(Document document, CompletionItem item, CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken) diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/OverrideCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/OverrideCompletionProvider.vb index d57901b07dd5a..0214f41b3aa66 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/OverrideCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/OverrideCompletionProvider.vb @@ -197,20 +197,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return False End Function - Protected Overrides Function GetTargetCaretPosition(caretTarget As SyntaxNode) As Integer + Protected Overrides Function GetTargetSelectionSpan(caretTarget As SyntaxNode) As TextSpan Dim node = DirectCast(caretTarget, SyntaxNode) ' MustOverride Sub | MustOverride Function: move to end of line Dim methodStatement = TryCast(node, MethodStatementSyntax) If methodStatement IsNot Nothing Then - Return methodStatement.GetLocation().SourceSpan.End + Return New TextSpan(methodStatement.GetLocation().SourceSpan.End, 0) End If Dim methodBlock = TryCast(node, MethodBlockBaseSyntax) If methodBlock IsNot Nothing Then Dim lastStatement = methodBlock.Statements.LastOrDefault() If lastStatement IsNot Nothing Then - Return lastStatement.GetLocation().SourceSpan.End + Return New TextSpan(lastStatement.GetLocation().SourceSpan.End, 0) End If End If @@ -220,12 +220,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers If firstAccessor IsNot Nothing Then Dim lastAccessorStatement = firstAccessor.Statements.LastOrDefault() If lastAccessorStatement IsNot Nothing Then - Return lastAccessorStatement.GetLocation().SourceSpan.End + Return New TextSpan(lastAccessorStatement.GetLocation().SourceSpan.End, 0) End If End If End If - Return -1 + Return New TextSpan(0, 0) End Function End Class End Namespace