' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.CommandHandlers
Imports Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement
Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.Shared.TestHooks
Imports Microsoft.CodeAnalysis.SignatureHelp
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.VisualStudio.Commanding
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Editor.Commanding.Commands

Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
    Friend Class TestState
        Inherits AbstractCommandHandlerTestState

        Private Const timeoutMs = 60000
        Friend ReadOnly EditorCompletionCommandHandler As ICommandHandler
        Friend ReadOnly CompletionPresenterProvider As ICompletionPresenterProvider

        Protected ReadOnly SessionTestState As IIntelliSenseTestState
        Private ReadOnly SignatureHelpBeforeCompletionCommandHandler As SignatureHelpBeforeCompletionCommandHandler
        Protected ReadOnly SignatureHelpAfterCompletionCommandHandler As SignatureHelpAfterCompletionCommandHandler
        Protected ReadOnly CompleteStatementCommandHandler As CompleteStatementCommandHandler
        Private ReadOnly FormatCommandHandler As FormatCommandHandler

        Public Shared ReadOnly CompositionWithoutCompletionTestParts As TestComposition = EditorTestCompositions.EditorFeaturesWpf.
            AddExcludedPartTypes(
                GetType(IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession)),
                GetType(FormatCommandHandler)).
            AddParts(
                GetType(TestSignatureHelpPresenter),
                GetType(IntelliSenseTestState),
                GetType(MockCompletionPresenterProvider))

        Friend ReadOnly Property CurrentSignatureHelpPresenterSession As TestSignatureHelpPresenterSession
            Get
                Return SessionTestState.CurrentSignatureHelpPresenterSession
            End Get
        End Property

        ' Do not call directly. Use TestStateFactory
        Friend Sub New(workspaceElement As XElement,
                       excludedTypes As IEnumerable(Of Type),
                       extraExportedTypes As IEnumerable(Of Type),
                       includeFormatCommandHandler As Boolean,
                       workspaceKind As String,
                       Optional makeSeparateBufferForCursor As Boolean = False,
                       Optional roles As ImmutableArray(Of String) = Nothing)
            MyBase.New(workspaceElement, GetComposition(excludedTypes, extraExportedTypes, includeFormatCommandHandler), workspaceKind:=workspaceKind, makeSeparateBufferForCursor, roles)

            ' Disable editor's responsive completion option to ensure a deterministic test behavior
            MyBase.TextView.Options.GlobalOptions.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, False)
            MyBase.TextView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart)

            Dim language = Me.Workspace.CurrentSolution.Projects.First().Language

            Me.SessionTestState = GetExportedValue(Of IIntelliSenseTestState)()

            Me.SignatureHelpBeforeCompletionCommandHandler = GetExportedValue(Of SignatureHelpBeforeCompletionCommandHandler)()

            Me.SignatureHelpAfterCompletionCommandHandler = GetExportedValue(Of SignatureHelpAfterCompletionCommandHandler)()
            Me.CompleteStatementCommandHandler = GetExportedValue(Of CompleteStatementCommandHandler)()

            Me.FormatCommandHandler = If(includeFormatCommandHandler, GetExportedValue(Of FormatCommandHandler)(), Nothing)
            Me.CompleteStatementCommandHandler = Workspace.ExportProvider.GetCommandHandler(Of CompleteStatementCommandHandler)(NameOf(CompleteStatementCommandHandler))

            CompletionPresenterProvider = GetExportedValues(Of ICompletionPresenterProvider)().
                Single(Function(e As ICompletionPresenterProvider) e.GetType().FullName = "Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense.MockCompletionPresenterProvider")
            EditorCompletionCommandHandler = GetExportedValues(Of ICommandHandler)().
                Single(Function(e As ICommandHandler) e.GetType().Name = PredefinedCompletionNames.CompletionCommandHandler)
        End Sub

        Private Overloads Shared Function GetComposition(
            excludedTypes As IEnumerable(Of Type),
            extraExportedTypes As IEnumerable(Of Type),
            includeFormatCommandHandler As Boolean) As TestComposition

            Dim composition = CompositionWithoutCompletionTestParts.
                AddExcludedPartTypes(excludedTypes).
                AddParts(extraExportedTypes)

            If includeFormatCommandHandler Then
                ' FormatCommandHandler would generally be included in the catalog, but is excluded from tests by adding
                ' it to the list of excluded part types. Here we validate the input state and restore the default
                ' behavior of the catalog by removing FormatCommandHandler from the excluded parts list.
                Assert.Contains(GetType(FormatCommandHandler).Assembly, composition.Assemblies)
                Assert.Contains(GetType(FormatCommandHandler), composition.ExcludedPartTypes)
                composition = composition.RemoveExcludedPartTypes(GetType(FormatCommandHandler))
            End If

            Return composition
        End Function

#Region "Editor Related Operations"

        Protected Overloads Sub ExecuteTypeCharCommand(args As TypeCharCommandArgs, finalHandler As Action, context As CommandExecutionContext, completionCommandHandler As IChainedCommandHandler(Of TypeCharCommandArgs))
            Dim sigHelpHandler = DirectCast(SignatureHelpBeforeCompletionCommandHandler, IChainedCommandHandler(Of TypeCharCommandArgs))
            Dim formatHandler = DirectCast(FormatCommandHandler, IChainedCommandHandler(Of TypeCharCommandArgs))

            If formatHandler Is Nothing Then
                sigHelpHandler.ExecuteCommand(
                    args, Sub() completionCommandHandler.ExecuteCommand(
                                    args, Sub() CompleteStatementCommandHandler.ExecuteCommand(args, finalHandler, context), context), context)
            Else
                formatHandler.ExecuteCommand(
                    args, Sub() sigHelpHandler.ExecuteCommand(
                                    args, Sub() completionCommandHandler.ExecuteCommand(
                                                    args, Sub() CompleteStatementCommandHandler.ExecuteCommand(args, finalHandler, context), context), context), context)
            End If
        End Sub

        Public Overloads Sub SendTab()
            Dim handler = GetHandler(Of IChainedCommandHandler(Of TabKeyCommandArgs))()
            MyBase.SendTab(Sub(a, n, c) handler.ExecuteCommand(a, n, c), Sub() EditorOperations.InsertText(vbTab))
        End Sub

        Public Overloads Sub SendReturn()
            Dim handler = GetHandler(Of IChainedCommandHandler(Of ReturnKeyCommandArgs))()
            MyBase.SendReturn(Sub(a, n, c) handler.ExecuteCommand(a, n, c), Sub() EditorOperations.InsertNewLine())
        End Sub

        Public Sub SendBackspaces(count As Integer)
            For i = 0 To count - 1
                Me.SendBackspace()
            Next
        End Sub

        Public Overrides Sub SendBackspace()
            Dim compHandler = GetHandler(Of IChainedCommandHandler(Of BackspaceKeyCommandArgs))()
            MyBase.SendBackspace(Sub(a, n, c) compHandler.ExecuteCommand(a, n, c), AddressOf MyBase.SendBackspace)
        End Sub

        Public Overrides Sub SendDelete()
            Dim compHandler = GetHandler(Of IChainedCommandHandler(Of DeleteKeyCommandArgs))()
            MyBase.SendDelete(Sub(a, n, c) compHandler.ExecuteCommand(a, n, c), AddressOf MyBase.SendDelete)
        End Sub

        Public Sub SendDeleteToSpecificViewAndBuffer(view As IWpfTextView, buffer As ITextBuffer)
            Dim compHandler = GetHandler(Of IChainedCommandHandler(Of DeleteKeyCommandArgs))()
            compHandler.ExecuteCommand(New DeleteKeyCommandArgs(view, buffer), AddressOf MyBase.SendDelete, TestCommandExecutionContext.Create())
        End Sub

        Private Overloads Sub ExecuteTypeCharCommand(args As TypeCharCommandArgs, finalHandler As Action, context As CommandExecutionContext)
            Dim compHandler = GetHandler(Of IChainedCommandHandler(Of TypeCharCommandArgs))()
            ExecuteTypeCharCommand(args, finalHandler, context, compHandler)
        End Sub

        Public Overloads Sub SendTypeChars(typeChars As String)
            MyBase.SendTypeChars(typeChars, Sub(a, n, c) ExecuteTypeCharCommand(a, n, c))
        End Sub

        Public Async Function SendTypeCharsAndWaitForUiRenderAsync(typeChars As String) As Task
            Dim uiRender = WaitForUIRenderedAsync()
            SendTypeChars(typeChars)
            Await uiRender
        End Function

        Public Overloads Sub SendEscape()
            MyBase.SendEscape(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, Sub() SignatureHelpAfterCompletionCommandHandler.ExecuteCommand(a, n, c), c), Sub() Return)
        End Sub

        Public Overloads Sub SendDownKey()
            MyBase.SendDownKey(
                Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, Sub() SignatureHelpAfterCompletionCommandHandler.ExecuteCommand(a, n, c), c),
                Sub()
                    EditorOperations.MoveLineDown(extendSelection:=False)
                End Sub)
        End Sub

        Public Overloads Sub SendUpKey()
            MyBase.SendUpKey(
                Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, Sub() SignatureHelpAfterCompletionCommandHandler.ExecuteCommand(a, n, c), c),
                Sub()
                    EditorOperations.MoveLineUp(extendSelection:=False)
                End Sub)
        End Sub

        Public Overloads Sub SendPageUp()
            Dim handler = DirectCast(EditorCompletionCommandHandler, ICommandHandler(Of PageUpKeyCommandArgs))
            MyBase.SendPageUp(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendCut()
            MyBase.SendCut(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendPaste()
            MyBase.SendPaste(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendInvokeCompletionList()
            MyBase.SendInvokeCompletionList(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Async Function SendInvokeCompletionListAndWaitForUiRenderAsync() As Task
            Dim uiRender = WaitForUIRenderedAsync()
            MyBase.SendInvokeCompletionList(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
            Await uiRender
        End Function

        Public Overloads Sub SendInsertSnippetCommand()
            MyBase.SendInsertSnippetCommand(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendSurroundWithCommand()
            MyBase.SendSurroundWithCommand(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendSave()
            MyBase.SendSave(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Sub SendSelectAll()

            MyBase.SendSelectAll(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overrides Sub SendDeleteWordToLeft()
            Dim compHandler = DirectCast(EditorCompletionCommandHandler, ICommandHandler(Of WordDeleteToStartCommandArgs))
            MyBase.SendWordDeleteToStart(Sub(a, n, c) compHandler.ExecuteCommand(a, n, c), AddressOf MyBase.SendDeleteWordToLeft)
        End Sub

        Public Overloads Sub SendToggleCompletionMode()
            Dim handler = DirectCast(EditorCompletionCommandHandler, ICommandHandler(Of ToggleCompletionModeCommandArgs))
            MyBase.SendToggleCompletionMode(Sub(a, n, c) handler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Protected Function GetHandler(Of T As ICommandHandler)() As T
            Return DirectCast(EditorCompletionCommandHandler, T)
        End Function

#End Region

#Region "Completion Operations"

        Public Async Function SendCommitUniqueCompletionListItemAsync() As Task
            Await WaitForAsynchronousOperationsAsync()

            ' When we send the commit completion list item, it processes asynchronously; we can find out when it's complete
            ' by seeing that either the items are updated or the list is dismissed. We'll use a TaskCompletionSource to track
            ' when it's done which will release an async token.
            Dim sessionComplete = New TaskCompletionSource(Of Object)()
            Dim asynchronousOperationListenerProvider = Workspace.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)()
            Dim asyncToken = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.CompletionSet) _
                .BeginAsyncOperation("SendCommitUniqueCompletionListItemAsync")

#Disable Warning BC42358 ' Because this call is not awaited, execution of the current method continues before the call is completed
            sessionComplete.Task.CompletesAsyncOperation(asyncToken)
#Enable Warning BC42358 ' Because this call is not awaited, execution of the current method continues before the call is completed

            Dim itemsUpdatedHandler = Sub(sender As Object, e As Data.ComputedCompletionItemsEventArgs)
                                          ' If there is 0 or more than one item left, then it means this was the filter operation that resulted and we're done. 
                                          ' Otherwise we know a Dismiss operation is coming so we should wait for it.
                                          If e.Items.Items.Count() <> 1 Then
                                              Task.Run(Sub()
                                                           Thread.Sleep(5000)
                                                           sessionComplete.TrySetResult(Nothing)
                                                       End Sub)
                                          End If
                                      End Sub

            Dim sessionDismissedHandler = Sub(sender As Object, e As EventArgs) sessionComplete.TrySetResult(Nothing)

            Dim session As IAsyncCompletionSession

            Dim addHandlers = Sub(sender As Object, e As Data.CompletionTriggeredEventArgs)
                                  AddHandler e.CompletionSession.ItemsUpdated, itemsUpdatedHandler
                                  AddHandler e.CompletionSession.Dismissed, sessionDismissedHandler
                                  session = e.CompletionSession
                              End Sub

            Dim asyncCompletionBroker As IAsyncCompletionBroker = GetExportedValue(Of IAsyncCompletionBroker)()
            session = asyncCompletionBroker.GetSession(TextView)
            If session Is Nothing Then
                AddHandler asyncCompletionBroker.CompletionTriggered, addHandlers
            Else
                ' A session was already active so we'll fake the event
                addHandlers(asyncCompletionBroker, New Data.CompletionTriggeredEventArgs(session, TextView))
            End If

            MyBase.SendCommitUniqueCompletionListItem(Sub(a, n, c) EditorCompletionCommandHandler.ExecuteCommand(a, n, c), Sub() Return)

            Await WaitForAsynchronousOperationsAsync()

            RemoveHandler session.ItemsUpdated, itemsUpdatedHandler
            RemoveHandler session.Dismissed, sessionDismissedHandler
            RemoveHandler asyncCompletionBroker.CompletionTriggered, addHandlers

            ' It's possible for the wait to bail and give up if it was clear nothing was completing; ensure we clean up our
            ' async token so as not to interfere with later tests.
            sessionComplete.TrySetResult(Nothing)
        End Function

        Public Async Function AssertNoCompletionSession() As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            If session Is Nothing Then
                Return
            End If

            If session.IsDismissed Then
                Return
            End If

            Dim completionItems = session.GetComputedItems(CancellationToken.None)

            ' During the computation we can explicitly dismiss the session or we can return no items.
            ' Each of these conditions mean that there is no active completion.
            Assert.True(session.IsDismissed OrElse completionItems.Items.Count() = 0, "AssertNoCompletionSession")
        End Function

        Public Sub AssertNoCompletionSessionWithNoBlock()
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            If session Is Nothing Then
                Return
            End If

            If session.IsDismissed Then
                Return
            End If

            ' If completionItems cannot be calculated in 5 seconds, no session exists.
            Dim task1 = Task.Delay(5000)
            Dim task2 = Task.Run(
                Sub()
                    Dim completionItems = session.GetComputedItems(CancellationToken.None)

                    ' In the non blocking mode, we are not interested for a session appeared later than in 5 seconds.
                    If task1.Status = TaskStatus.Running Then
                        ' During the computation we can explicitly dismiss the session or we can return no items.
                        ' Each of these conditions mean that there is no active completion.
                        Assert.True(session.IsDismissed OrElse completionItems.Items.Count() = 0)
                    End If
                End Sub)

            Task.WaitAny(task1, task2)
        End Sub

        Public Async Function GetCompletionSession(Optional projectionsView As ITextView = Nothing) As Task(Of IAsyncCompletionSession)
            Await WaitForAsynchronousOperationsAsync()
            Dim view = If(projectionsView, TextView)

            Return GetExportedValue(Of IAsyncCompletionBroker)().GetSession(view)
        End Function

        Public Async Function AssertCompletionSession(Optional projectionsView As ITextView = Nothing) As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim view = If(projectionsView, TextView)

            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(view)
            Assert.NotNull(session)
        End Function

        Public Async Function AssertCompletionItemsDoNotContainAny(ParamArray displayText As String()) As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim items = GetCompletionItems()
            Assert.False(displayText.Any(Function(v) items.Any(Function(i) i.DisplayText = v)))
        End Function

        Public Async Function AssertCompletionItemsContainAll(ParamArray displayText As String()) As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim items = GetCompletionItems()
            Assert.True(displayText.All(Function(v) items.Any(Function(i) i.DisplayText = v)))
        End Function

        Public Async Function AssertCompletionItemsContain(displayText As String, displayTextSuffix As String) As Task
            Await AssertCompletionItemsContain(Function(i) i.DisplayText = displayText AndAlso i.DisplayTextSuffix = displayTextSuffix)
        End Function

        Public Async Function AssertCompletionItemsContain(predicate As Func(Of CompletionItem, Boolean)) As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim items = GetCompletionItems()
            Assert.True(items.Any(predicate))
        End Function

        Public Sub AssertItemsInOrder(expectedOrder As String())
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim items = session.GetComputedItems(CancellationToken.None).Items
            Assert.Equal(expectedOrder.Count, items.Count)
            For i = 0 To expectedOrder.Count - 1
                Assert.Equal(expectedOrder(i), items(i).DisplayText)
            Next
        End Sub

        Public Sub AssertItemsInOrder(expectedOrder As (String, String)())
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim items = session.GetComputedItems(CancellationToken.None).Items
            Assert.Equal(expectedOrder.Count, items.Count)
            For i = 0 To expectedOrder.Count - 1
                Assert.Equal(expectedOrder(i).Item1, items(i).DisplayText)
                Assert.Equal(expectedOrder(i).Item2, items(i).Suffix)
            Next
        End Sub

        Public Async Function AssertSelectedCompletionItem(
                Optional displayText As String = Nothing,
                Optional displayTextSuffix As String = Nothing,
                Optional description As String = Nothing,
                Optional isSoftSelected As Boolean? = Nothing,
                Optional isHardSelected As Boolean? = Nothing,
                Optional shouldFormatOnCommit As Boolean? = Nothing,
                Optional inlineDescription As String = Nothing,
                Optional automationText As String = Nothing,
                Optional projectionsView As ITextView = Nothing) As Task

            Await WaitForAsynchronousOperationsAsync()
            Dim view = If(projectionsView, TextView)

            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(view)
            Assert.NotNull(session)
            Dim items = session.GetComputedItems(CancellationToken.None)

            If isSoftSelected.HasValue Then
                If isSoftSelected.Value Then
                    Assert.True(items.UsesSoftSelection, "Current completion is not soft-selected. Expected: soft-selected")
                Else
                    Assert.False(items.UsesSoftSelection, "Current completion is soft-selected. Expected: not soft-selected")
                End If
            End If

            If isHardSelected.HasValue Then
                If isHardSelected.Value Then
                    Assert.True(Not items.UsesSoftSelection, "Current completion is not hard-selected. Expected: hard-selected")
                Else
                    Assert.True(items.UsesSoftSelection, "Current completion is hard-selected. Expected: not hard-selected")
                End If
            End If

            If displayText IsNot Nothing Then
                Assert.NotNull(items.SelectedItem)
                If displayTextSuffix IsNot Nothing Then
                    Assert.NotNull(items.SelectedItem)
                    Assert.Equal(displayText + displayTextSuffix, items.SelectedItem.DisplayText)
                Else
                    Assert.Equal(displayText, items.SelectedItem.DisplayText)
                End If
            End If

            If shouldFormatOnCommit.HasValue Then
                Assert.Equal(shouldFormatOnCommit.Value, GetRoslynCompletionItem(items.SelectedItem).Rules.FormatOnCommit)
            End If

            If description IsNot Nothing Then
                Dim itemDescription = Await GetSelectedItemDescriptionAsync()
                Assert.Equal(description, itemDescription.Text)
            End If

            If inlineDescription IsNot Nothing Then
                Assert.Equal(inlineDescription, items.SelectedItem.Suffix)
            End If

            If automationText IsNot Nothing Then
                Assert.Equal(automationText, items.SelectedItem.AutomationText)
            End If
        End Function

        Public Async Function GetSelectedItemDescriptionAsync() As Task(Of CompletionDescription)
            Dim document = Me.Workspace.CurrentSolution.Projects.First().Documents.First()
            Dim service = CompletionService.GetService(document)
            Dim roslynItem = GetSelectedItem()
            Dim options = CompletionOptions.Default
            Return Await service.GetDescriptionAsync(document, roslynItem, options, SymbolDescriptionOptions.Default)
        End Function

        Public Sub AssertCompletionItemExpander(isAvailable As Boolean, isSelected As Boolean)
            Dim presenter = DirectCast(CompletionPresenterProvider.GetOrCreate(Me.TextView), MockCompletionPresenter)
            Dim expander = presenter.GetExpander()
            If Not isAvailable Then
                Assert.False(isSelected)
                Assert.Null(expander)
            Else
                Assert.NotNull(expander)
                Assert.Equal(expander.IsSelected, isSelected)
            End If
        End Sub

        Public Async Function SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected As Boolean) As Task
            Dim uiRender = WaitForUIRenderedAsync()
            Dim presenter = DirectCast(CompletionPresenterProvider.GetOrCreate(Me.TextView), MockCompletionPresenter)
            Dim expander = presenter.GetExpander()
            Assert.NotNull(expander)
            presenter.SetExpander(isSelected)
            Await uiRender
        End Function

        Public Async Function AssertSessionIsNothingOrNoCompletionItemLike(text As String) As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            If Not session Is Nothing Then
                Await AssertCompletionItemsDoNotContainAny(text)
            End If
        End Function

        Public Function GetSelectedItem() As CompletionItem
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim items = session.GetComputedItems(CancellationToken.None)
            Return GetRoslynCompletionItem(items.SelectedItem)
        End Function

        Public Sub CalculateItemsIfSessionExists()
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            If session IsNot Nothing Then
                Dim item = session.GetComputedItems(CancellationToken.None).SelectedItem
            End If
        End Sub

        Public Function GetCompletionItems() As IList(Of CompletionItem)
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Return session.GetComputedItems(CancellationToken.None).Items.Select(Function(item) GetRoslynCompletionItem(item)).ToList()
        End Function

        Private Shared Function GetRoslynCompletionItem(item As Data.CompletionItem) As CompletionItem
            If (item Is Nothing) Then
                Return Nothing
            End If

            Dim roslynItemData As CompletionItemData = Nothing
            If (CompletionItemData.TryGetData(item, roslynItemData) = False) Then
                Return Nothing
            End If
            Return roslynItemData.RoslynItem
        End Function

        Public Async Function RaiseFiltersChangedAndWaitForUiRenderAsync(args As ImmutableArray(Of Data.CompletionFilterWithState)) As Task
            Dim uiRender = WaitForUIRenderedAsync()
            Dim presenter = DirectCast(CompletionPresenterProvider.GetOrCreate(Me.TextView), MockCompletionPresenter)
            Dim newArgs = New Data.CompletionFilterChangedEventArgs(args)
            presenter.TriggerFiltersChanged(Me, newArgs)
            Await uiRender
        End Function

        Public Function GetCompletionItemFilters() As ImmutableArray(Of Data.CompletionFilterWithState)
            Dim presenter = DirectCast(CompletionPresenterProvider.GetOrCreate(Me.TextView), MockCompletionPresenter)
            Return presenter.GetFilters()
        End Function

        Public Function HasSuggestedItem() As Boolean
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim computedItems = session.GetComputedItems(CancellationToken.None)
            Return computedItems.SuggestionItem IsNot Nothing
        End Function

        Public Sub AssertSuggestedItemSelected(displayText As String)
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim computedItems = session.GetComputedItems(CancellationToken.None)
            Assert.True(computedItems.SuggestionItemSelected)
            Assert.Equal(computedItems.SuggestionItem.DisplayText, displayText)
        End Sub

        Public Function IsSoftSelected() As Boolean
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Assert.NotNull(session)
            Dim computedItems = session.GetComputedItems(CancellationToken.None)
            Return computedItems.UsesSoftSelection
        End Function

        Public Sub SendSelectCompletionItem(displayText As String)
            Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
            Dim operations = DirectCast(session, IAsyncCompletionSessionOperations)
            operations.SelectCompletionItem(session.GetComputedItems(CancellationToken.None).Items.Single(Function(i) i.DisplayText = displayText))
        End Sub

        Public Async Function WaitForUIRenderedAsync() As Task
            Await WaitForAsynchronousOperationsAsync()
            Dim tcs = New TaskCompletionSource(Of Boolean)
            Dim presenter = DirectCast(CompletionPresenterProvider.GetOrCreate(TextView), MockCompletionPresenter)
            Dim uiUpdated As EventHandler(Of Data.CompletionItemSelectedEventArgs)

            uiUpdated = Sub()
                            RemoveHandler presenter.UiUpdated, uiUpdated
                            tcs.TrySetResult(True)
                        End Sub

            AddHandler presenter.UiUpdated, uiUpdated
            Dim ct = New CancellationTokenSource(timeoutMs)
            ct.Token.Register(Sub() tcs.TrySetCanceled(), useSynchronizationContext:=False)

            Await tcs.Task.ConfigureAwait(True)
        End Function

        Public Overloads Sub SendTypeCharsToSpecificViewAndBuffer(typeChars As String, view As IWpfTextView, buffer As ITextBuffer)
            For Each ch In typeChars
                Dim localCh = ch
                ExecuteTypeCharCommand(New TypeCharCommandArgs(view, buffer, localCh), Sub() EditorOperations.InsertText(localCh.ToString()), TestCommandExecutionContext.Create())
            Next
        End Sub

        Public Async Function AssertLineTextAroundCaret(expectedTextBeforeCaret As String, expectedTextAfterCaret As String) As Task
            Await WaitForAsynchronousOperationsAsync()

            Dim actual = GetLineTextAroundCaretPosition()

            Assert.Equal(expectedTextBeforeCaret, actual.TextBeforeCaret)
            Assert.Equal(expectedTextAfterCaret, actual.TextAfterCaret)
        End Function

        Public Sub NavigateToDisplayText(targetText As String)
            Dim currentText = GetSelectedItem().DisplayText

            ' GetComputedItems provided by the Editor for tests does not guarantee that 
            ' the order of items match the order of items actually displayed in the completion popup.
            ' For example, they put starred items (intellicode) below non-starred ones.
            ' And the order they display those items in the UI is opposite.
            ' Therefore, we do the full traverse: down to the bottom and if not found up to the top.
            Do While currentText <> targetText
                SendDownKey()
                Dim newText = GetSelectedItem().DisplayText
                If currentText = newText Then
                    ' Nothing found on going down. Try going up
                    Do While currentText <> targetText
                        SendUpKey()
                        newText = GetSelectedItem().DisplayText
                        Assert.True(newText <> currentText, "Reached the bottom, then the top and didn't find the match")
                        currentText = newText
                    Loop
                End If

                currentText = newText
            Loop
        End Sub

#End Region

#Region "Signature Help Operations"

        Public Overloads Sub SendInvokeSignatureHelp()
            Dim handler = DirectCast(SignatureHelpBeforeCompletionCommandHandler, IChainedCommandHandler(Of InvokeSignatureHelpCommandArgs))
            MyBase.SendInvokeSignatureHelp(Sub(a, n, c) handler.ExecuteCommand(a, n, c), Sub() Return)
        End Sub

        Public Overloads Async Function AssertNoSignatureHelpSession(Optional block As Boolean = True) As Task
            If block Then
                Await WaitForAsynchronousOperationsAsync()
            End If

            Assert.Null(Me.CurrentSignatureHelpPresenterSession)
        End Function

        Public Overloads Async Function AssertSignatureHelpSession() As Task
            Await WaitForAsynchronousOperationsAsync()
            Assert.NotNull(Me.CurrentSignatureHelpPresenterSession)
        End Function

        Public Overloads Function GetSignatureHelpItems() As IList(Of SignatureHelpItem)
            Return CurrentSignatureHelpPresenterSession.SignatureHelpItems
        End Function

        Public Async Function AssertSignatureHelpItemsContainAll(displayText As String()) As Task
            Await WaitForAsynchronousOperationsAsync()
            Assert.True(displayText.All(Function(v) CurrentSignatureHelpPresenterSession.SignatureHelpItems.Any(
                                            Function(i) GetDisplayText(i, CurrentSignatureHelpPresenterSession.SelectedParameter.Value) = v)))
        End Function

        Public Async Function AssertSelectedSignatureHelpItem(Optional displayText As String = Nothing,
                               Optional documentation As String = Nothing,
                               Optional selectedParameter As String = Nothing) As Task
            Await WaitForAsynchronousOperationsAsync()

            If displayText IsNot Nothing Then
                Assert.Equal(displayText, GetDisplayText(Me.CurrentSignatureHelpPresenterSession.SelectedItem, Me.CurrentSignatureHelpPresenterSession.SelectedParameter.Value))
            End If

            If documentation IsNot Nothing Then
                Assert.Equal(documentation, Me.CurrentSignatureHelpPresenterSession.SelectedItem.DocumentationFactory(CancellationToken.None).GetFullText())
            End If

            If selectedParameter IsNot Nothing Then
                Assert.Equal(selectedParameter, GetDisplayText(
                    Me.CurrentSignatureHelpPresenterSession.SelectedItem.Parameters(
                        Me.CurrentSignatureHelpPresenterSession.SelectedParameter.Value).DisplayParts))
            End If
        End Function
#End Region

#Region "Helpers"

        Private Shared Function GetDisplayText(item As SignatureHelpItem, selectedParameter As Integer) As String
            Dim suffix = If(selectedParameter < item.Parameters.Count,
                            GetDisplayText(item.Parameters(selectedParameter).SuffixDisplayParts),
                            String.Empty)
            Return String.Join(
                String.Empty,
                GetDisplayText(item.PrefixDisplayParts),
                String.Join(
                    GetDisplayText(item.SeparatorDisplayParts),
                    item.Parameters.Select(Function(p) GetDisplayText(p.DisplayParts))),
                GetDisplayText(item.SuffixDisplayParts),
                suffix)
        End Function

        Private Shared Function GetDisplayText(parts As IEnumerable(Of TaggedText)) As String
            Return String.Join(String.Empty, parts.Select(Function(p) p.ToString()))
        End Function

#End Region

    End Class
End Namespace
