diff --git a/src/EditorFeatures/Core/Extensibility/SignatureHelp/ISignatureHelpPresenterSession.cs b/src/EditorFeatures/Core/Extensibility/SignatureHelp/ISignatureHelpPresenterSession.cs index 31b09d113f9aeedcd843a57e56816944dac75db2..01fb8a69e8c8e7d98a7ce563a320c833721557f5 100644 --- a/src/EditorFeatures/Core/Extensibility/SignatureHelp/ISignatureHelpPresenterSession.cs +++ b/src/EditorFeatures/Core/Extensibility/SignatureHelp/ISignatureHelpPresenterSession.cs @@ -13,5 +13,7 @@ internal interface ISignatureHelpPresenterSession : IIntelliSensePresenterSessio void SelectNextItem(); event EventHandler ItemSelected; + + bool EditorSessionIsActive { get; } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Controller_NavigationKeys.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Controller_NavigationKeys.cs index aab30df09a581f95039ded3c5be242b43a3203b3..4519729167d1ca474b0af102a9ca3eb7ebeb649d 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Controller_NavigationKeys.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Controller_NavigationKeys.cs @@ -28,6 +28,15 @@ private bool ChangeSelection(Action computationAction) return false; } + // If we haven't started our editor session yet, just abort. + // The user hasn't seen a SigHelp presentation yet, so they're + // probably not trying to change the currently visible overload. + if (!sessionOpt.PresenterSession.EditorSessionIsActive) + { + DismissSessionIfActive(); + return false; + } + // If we've finished computing the items then use the navigation commands to change the // selected item. Otherwise, the user was just typing and is now moving through the // file. In this case stop everything we're doing. diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs index 6dbe07f8c2720c34e72c914b4b9a350f73f3a188..6cb8dbb920988eaa29cb90bff683f16041a85074 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs @@ -31,6 +31,8 @@ private class SignatureHelpPresenterSession : ForegroundThreadAffinitizedObject, private ISignatureHelpSession _editorSessionOpt; private bool _ignoreSelectionStatusChangedEvent; + public bool EditorSessionIsActive => _editorSessionOpt?.IsDismissed == false; + public SignatureHelpPresenterSession( ISignatureHelpBroker sigHelpBroker, ITextView textView, diff --git a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb index c19ca4a9512fae834f4692440bdd31aefe69bbb0..cde1dd8f9feb4f647d5acb38c82417ed9b1f44c2 100644 --- a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb @@ -1,9 +1,9 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -Imports System.ComponentModel.Composition.Hosting Imports System.Runtime.CompilerServices Imports System.Threading Imports System.Threading.Tasks +Imports System.Windows.Threading Imports Microsoft.CodeAnalysis.Editor.Commands Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp @@ -13,7 +13,6 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Language.Intellisense Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor -Imports Microsoft.VisualStudio.Text.Projection Imports Moq #Disable Warning RS0007 ' Avoid zero-length array allocations. This is non-shipping test code. @@ -93,6 +92,79 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense GetMocks(controller).PresenterSession.Verify(Sub(p) p.Dismiss(), Times.Once) End Sub + + + Public Sub DownKeyShouldNotBlockOnModelComputation() + Dim mre = New ManualResetEvent(False) + Dim controller = CreateController(items:=CreateItems(2), waitForPresentation:=False) + Dim slowProvider = New Mock(Of ISignatureHelpProvider) + slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), It.IsAny(Of CancellationToken))) _ + .Returns(Function() + mre.WaitOne() + Return Task.FromResult(New SignatureHelpItems(CreateItems(2), TextSpan.FromBounds(0, 0), selectedItem:=0, argumentIndex:=0, argumentCount:=0, argumentName:=Nothing)) + End Function) + + + Dim handled = controller.TryHandleDownKey() + + Assert.False(handled) + End Sub + + + + Public Sub UpKeyShouldNotBlockOnModelComputation() + Dim mre = New ManualResetEvent(False) + Dim controller = CreateController(items:=CreateItems(2), waitForPresentation:=False) + Dim slowProvider = New Mock(Of ISignatureHelpProvider) + slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), It.IsAny(Of CancellationToken))) _ + .Returns(Function() + mre.WaitOne() + Return Task.FromResult(New SignatureHelpItems(CreateItems(2), TextSpan.FromBounds(0, 0), selectedItem:=0, argumentIndex:=0, argumentCount:=0, argumentName:=Nothing)) + End Function) + + + Dim handled = controller.TryHandleUpKey() + + Assert.False(handled) + End Sub + + + + Public Async Function UpKeyShouldBlockOnRecomputationAfterPresentation() As Task + Dim dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher + Dim worker = Async Function() + Dim slowProvider = New Mock(Of ISignatureHelpProvider) + slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), It.IsAny(Of CancellationToken))) _ + .Returns(Task.FromResult(New SignatureHelpItems(CreateItems(2), TextSpan.FromBounds(0, 0), selectedItem:=0, argumentIndex:=0, argumentCount:=0, argumentName:=Nothing))) + + Dim controller = dispatcher.Invoke(Function() CreateController(provider:=slowProvider.Object, waitForPresentation:=True)) + + ' Update session so that providers are requeried. + ' SlowProvider now blocks on the checkpoint's task. + Dim checkpoint = New Checkpoint() + slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), It.IsAny(Of CancellationToken))) _ + .Returns(Function() + checkpoint.Task.Wait() + Return Task.FromResult(New SignatureHelpItems(CreateItems(2), TextSpan.FromBounds(0, 2), selectedItem:=0, argumentIndex:=0, argumentCount:=0, argumentName:=Nothing)) + End Function) + + dispatcher.Invoke(Sub() DirectCast(controller, ICommandHandler(Of TypeCharCommandArgs)).ExecuteCommand( + New TypeCharCommandArgs(CreateMock(Of ITextView), CreateMock(Of ITextBuffer), " "c), + Sub() GetMocks(controller).Buffer.Insert(0, " "))) + + Dim handled = dispatcher.InvokeAsync(Function() controller.TryHandleUpKey()) ' Send the controller an up key, which should block on the computation + checkpoint.Release() ' Allow slowprovider to finish + Await handled.Task.ConfigureAwait(False) + + ' We expect 2 calls to the presenter (because we had an existing presentation session when we started the second computation). + Assert.True(handled.Result) + GetMocks(controller).PresenterSession.Verify(Sub(p) p.PresentItems(It.IsAny(Of ITrackingSpan), It.IsAny(Of IList(Of SignatureHelpItem)), + It.IsAny(Of SignatureHelpItem), It.IsAny(Of Integer?)), Times.Exactly(2)) + End Function + Await worker().ConfigureAwait(False) + + End Function + Public Sub DownKeyShouldNavigateWhenThereAreMultipleItems() Dim controller = CreateController(items:=CreateItems(2), waitForPresentation:=True) @@ -195,6 +267,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim presenter = New Mock(Of IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession)) With {.DefaultValue = DefaultValue.Mock} presenterSession = If(presenterSession, New Mock(Of ISignatureHelpPresenterSession) With {.DefaultValue = DefaultValue.Mock}) presenter.Setup(Function(p) p.CreateSession(It.IsAny(Of ITextView), It.IsAny(Of ITextBuffer), It.IsAny(Of ISignatureHelpSession))).Returns(presenterSession.Object) + presenterSession.Setup(Sub(p) p.PresentItems(It.IsAny(Of ITrackingSpan), It.IsAny(Of IList(Of SignatureHelpItem)), It.IsAny(Of SignatureHelpItem), It.IsAny(Of Integer?))) _ + .Callback(Sub() presenterSession.SetupGet(Function(p) p.EditorSessionIsActive).Returns(True)) + Dim controller = New Controller( view.Object, diff --git a/src/EditorFeatures/Test2/IntelliSense/TestSignatureHelpPresenterSession.vb b/src/EditorFeatures/Test2/IntelliSense/TestSignatureHelpPresenterSession.vb index 2621c8a602203485ecf129aebb8a69648b97cff4..22b9d359b47cf49eed6bd5a1a43b1d3d727f9a47 100644 --- a/src/EditorFeatures/Test2/IntelliSense/TestSignatureHelpPresenterSession.vb +++ b/src/EditorFeatures/Test2/IntelliSense/TestSignatureHelpPresenterSession.vb @@ -13,6 +13,13 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Public SignatureHelpItems As IList(Of SignatureHelpItem) Public SelectedItem As SignatureHelpItem Public SelectedParameter As Integer? + Private presented As Boolean = False + + Public ReadOnly Property EditorSessionIsActive As Boolean Implements ISignatureHelpPresenterSession.EditorSessionIsActive + Get + Return presented + End Get + End Property Public Event Dismissed As EventHandler(Of EventArgs) Implements ISignatureHelpPresenterSession.Dismissed Public Event ItemSelected As EventHandler(Of SignatureHelpItemEventArgs) Implements ISignatureHelpPresenterSession.ItemSelected @@ -30,10 +37,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Me.SignatureHelpItems = signatureHelpItems Me.SelectedItem = selectedItem Me.SelectedParameter = selectedParameter + Me.presented = True End Sub Public Sub Dismiss() Implements ISignatureHelpPresenterSession.Dismiss _testState.CurrentSignatureHelpPresenterSession = Nothing + Me.presented = False End Sub Public Sub SetSelectedItem(item As SignatureHelpItem)