diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs index f1243358170a..d05d04721374 100644 --- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs @@ -23,6 +23,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -479,7 +480,7 @@ public virtual bool TryInsertExpansion(int startPositionInSubjectBuffer, int end } var buffer = EditorAdaptersFactoryService.GetBufferAdapter(textViewModel.DataBuffer); - if (buffer == null || !(buffer is IVsExpansion expansion)) + if (buffer is not IVsExpansion expansion) { return false; } @@ -495,6 +496,12 @@ public virtual bool TryInsertExpansion(int startPositionInSubjectBuffer, int end iEndIndex = endIndex }; + if (TryInsertArgumentCompletionSnippet(triggerSpan, dataBufferSpan, expansion, textSpan, cancellationToken)) + { + Debug.Assert(_state.IsFullMethodCallSnippet); + return true; + } + if (expansion.InsertExpansion(textSpan, textSpan, this, LanguageServiceGuid, out _state._expansionSession) == VSConstants.S_OK) { // This expansion is not derived from a symbol, so make sure the state isn't tracking any symbol @@ -503,6 +510,11 @@ public virtual bool TryInsertExpansion(int startPositionInSubjectBuffer, int end return true; } + return false; + } + + private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, SnapshotSpan dataBufferSpan, IVsExpansion expansion, VsTextSpan textSpan, CancellationToken cancellationToken) + { if (!(SubjectBuffer.GetFeatureOnOffOption(CompletionOptions.EnableArgumentCompletionSnippets) ?? false)) { // Argument completion snippets are not enabled @@ -521,6 +533,7 @@ public virtual bool TryInsertExpansion(int startPositionInSubjectBuffer, int end var methodSymbols = symbols.OfType().ToImmutableArray(); if (methodSymbols.Any()) { + // This is the method name as it appears in source text var methodName = dataBufferSpan.GetText(); var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty, cancellationToken); @@ -530,7 +543,7 @@ public virtual bool TryInsertExpansion(int startPositionInSubjectBuffer, int end if (expansion.InsertSpecificExpansion(doc, textSpan, this, LanguageServiceGuid, pszRelativePath: null, out _state._expansionSession) == VSConstants.S_OK) { Debug.Assert(_state._expansionSession != null); - _state._methodNameForInsertFullMethodCall = methodName; + _state._methodNameForInsertFullMethodCall = methodSymbols.First().Name; Debug.Assert(_state._method == null); if (_signatureHelpControllerProvider.GetController(TextView, SubjectBuffer) is { } controller) @@ -722,7 +735,8 @@ private static async Task> GetReferencedSymbolsToLeftOfC CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var token = await semanticModel.SyntaxTree.GetTouchingTokenAsync(caretPosition.Position, cancellationToken).ConfigureAwait(false); + + var token = await semanticModel.SyntaxTree.GetTouchingWordAsync(caretPosition.Position, document.GetRequiredLanguageService(), cancellationToken).ConfigureAwait(false); var semanticInfo = semanticModel.GetSemanticInfo(token, document.Project.Solution.Workspace, cancellationToken); return semanticInfo.ReferencedSymbols; } @@ -1167,8 +1181,8 @@ private sealed class State public IVsExpansionSession? _expansionSession; /// - /// The name of the method that we have invoked insert full method call on. Null if the session is - /// a regular snippets session. + /// The symbol name of the method that we have invoked insert full method call on; or + /// if there is no active snippet session or the active session is a regular snippets session. /// public string? _methodNameForInsertFullMethodCall; diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs index 705cb6177076..99237db591d1 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs @@ -57,6 +57,83 @@ public void Method() VisualStudio.Editor.Verify.CurrentLineText("f.ToString()$$", assertCaretPosition: true); } + [WpfFact] + public void TabTabCompleteObjectEquals() + { + SetUpEditor(@" +public class Test +{ + public void Method() + { + $$ + } +} +"); + + VisualStudio.Editor.SendKeys("object.Equ"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("object.Equals$$", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + VisualStudio.Editor.Verify.CurrentLineText("object.Equals(null$$)", assertCaretPosition: true); + } + + [WpfFact] + public void TabTabCompleteNewObject() + { + SetUpEditor(@" +public class Test +{ + public void Method() + { + var value = $$ + } +} +"); + + VisualStudio.Editor.SendKeys("new obje"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("var value = new object$$", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + VisualStudio.Editor.Verify.CurrentLineText("var value = new object($$)", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("var value = new object()$$", assertCaretPosition: true); + } + + [WpfFact] + public void TabTabBeforeSemicolon() + { + SetUpEditor(@" +public class Test +{ + private object f; + + public void Method() + { + $$; + } +} +"); + + VisualStudio.Editor.SendKeys("f.ToSt"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("f.ToString$$;", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + VisualStudio.Editor.Verify.CurrentLineText("f.ToString($$);", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("f.ToString()$$;", assertCaretPosition: true); + } + [WpfFact] public void TabTabCompletionWithArguments() { diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicArgumentProvider.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicArgumentProvider.cs index 3dd8e176c3b1..86d0af4ea63a 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicArgumentProvider.cs @@ -55,6 +55,51 @@ End Class VisualStudio.Editor.Verify.CurrentLineText("f.ToString()$$", assertCaretPosition: true); } + [WpfFact] + public void TabTabCompleteObjectEquals() + { + SetUpEditor(@" +Public Class Test + Public Sub Method() + $$ + End Sub +End Class +"); + + VisualStudio.Editor.SendKeys("Object.Equ"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("Object.Equals$$", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + VisualStudio.Editor.Verify.CurrentLineText("Object.Equals(Nothing$$)", assertCaretPosition: true); + } + + [WpfFact] + public void TabTabCompleteNewObject() + { + SetUpEditor(@" +Public Class Test + Public Sub Method() + Dim value = $$ + End Sub +End Class +"); + + VisualStudio.Editor.SendKeys("New Obje"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("Dim value = New Object$$", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + VisualStudio.Editor.Verify.CurrentLineText("Dim value = New Object($$)", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("Dim value = New Object()$$", assertCaretPosition: true); + } + [WpfFact] public void TabTabCompletionWithArguments() { diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicAutomaticBraceCompletion.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicAutomaticBraceCompletion.cs index 9684b520b409..324d5b96ff05 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicAutomaticBraceCompletion.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicAutomaticBraceCompletion.cs @@ -5,12 +5,12 @@ #nullable disable using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Roslyn.Test.Utilities; using Xunit; -using Xunit.Abstractions; namespace Roslyn.VisualStudio.IntegrationTests.VisualBasic { @@ -24,9 +24,11 @@ public BasicAutomaticBraceCompletion(VisualStudioInstanceFactory instanceFactory { } - [WpfFact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] - public void Braces_InsertionAndTabCompleting() + [WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void Braces_InsertionAndTabCompleting(bool argumentCompletion) { + VisualStudio.Workspace.SetArgumentCompletionSnippetsOption(argumentCompletion); + SetUpEditor(@" Class C Sub Goo() @@ -42,7 +44,21 @@ End Sub VirtualKey.Escape, VirtualKey.Tab); - VisualStudio.Editor.Verify.CurrentLineText("Dim x = {New Object}$$", assertCaretPosition: true); + if (argumentCompletion) + { + VisualStudio.Editor.Verify.CurrentLineText("Dim x = {New Object($$)}", assertCaretPosition: true); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("Dim x = {New Object()$$}", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("Dim x = {New Object()}$$", assertCaretPosition: true); + } + else + { + VisualStudio.Editor.Verify.CurrentLineText("Dim x = {New Object}$$", assertCaretPosition: true); + } } [WpfFact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs index 29d06af07a00..4049fe63c997 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; @@ -178,6 +179,29 @@ public void CleanUpWorkspace() _visualStudioWorkspace.TestHookPartialSolutionsDisabled = true; }); + /// + /// Reset options that are manipulated by integration tests back to their default values. + /// + public void ResetOptions() + { + ResetOption(CompletionOptions.EnableArgumentCompletionSnippets); + return; + + // Local function + void ResetOption(IOption option) + { + if (option is IPerLanguageOption) + { + SetOption(new OptionKey(option, LanguageNames.CSharp), option.DefaultValue); + SetOption(new OptionKey(option, LanguageNames.VisualBasic), option.DefaultValue); + } + else + { + SetOption(new OptionKey(option), option.DefaultValue); + } + } + } + public void CleanUpWaitingService() => InvokeOnUIThread(cancellationToken => { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs index a3e3f1773e8a..197ed7abd811 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs @@ -56,6 +56,9 @@ public void WaitForAllAsyncOperationsOrFail(TimeSpan timeout, params string[] fe public void CleanUpWorkspace() => _inProc.CleanUpWorkspace(); + public void ResetOptions() + => _inProc.ResetOptions(); + public void CleanUpWaitingService() => _inProc.CleanUpWaitingService(); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs index b4b41f6c123e..67fca07709bf 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs @@ -15,7 +15,6 @@ using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess; -using Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature; using Process = System.Diagnostics.Process; namespace Microsoft.VisualStudio.IntegrationTest.Utilities @@ -235,6 +234,7 @@ public void CleanUp() // Prevent the start page from showing after each solution closes StartPage.SetEnabled(false); + Workspace.ResetOptions(); } public void Close(bool exitHostProcess = true)