File: Completion\CompletionProviders\CrefCompletionProvider.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' 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.Composition
Imports System.Text
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.ErrorReporting
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
    <ExportCompletionProvider(NameOf(CrefCompletionProvider), LanguageNames.VisualBasic)>
    <ExtensionOrder(After:=NameOf(PartialTypeCompletionProvider))>
    <[Shared]>
    Partial Friend Class CrefCompletionProvider
        Inherits AbstractCrefCompletionProvider
 
        Private Shared ReadOnly s_crefFormat As SymbolDisplayFormat =
            New SymbolDisplayFormat(
                globalNamespaceStyle:=SymbolDisplayGlobalNamespaceStyle.Omitted,
                typeQualificationStyle:=SymbolDisplayTypeQualificationStyle.NameOnly,
                propertyStyle:=SymbolDisplayPropertyStyle.NameOnly,
                genericsOptions:=SymbolDisplayGenericsOptions.IncludeTypeParameters,
                miscellaneousOptions:=SymbolDisplayMiscellaneousOptions.UseSpecialTypes)
 
        Private Shared ReadOnly s_minimalParameterTypeFormat As SymbolDisplayFormat =
            SymbolDisplayFormat.MinimallyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.ExpandValueTuple)
 
        Private _testSpeculativeNodeCallbackOpt As Action(Of SyntaxNode)
 
        Public Overrides Function IsInsertionTrigger(text As SourceText, characterPosition As Integer, options As CompletionOptions) As Boolean
            Return CompletionUtilities.IsDefaultTriggerCharacter(text, characterPosition, options)
        End Function
 
        Friend Overrides ReadOnly Property Language As String
            Get
                Return LanguageNames.VisualBasic
            End Get
        End Property
 
        Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerChars
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        Public Overrides Async Function ProvideCompletionsAsync(context As CompletionContext) As Task
            Try
                Dim document = context.Document
                Dim position = context.Position
                Dim cancellationToken = context.CancellationToken
 
                Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
                Dim token = tree.GetTargetToken(position, cancellationToken)
 
                If IsCrefTypeParameterContext(token) Then
                    Return
                End If
 
                ' To get a Speculative SemanticModel (which is much faster), we need to 
                ' walk up to the node the DocumentationTrivia is attached to.
                Dim parentNode = token.Parent?.FirstAncestorOrSelf(Of DocumentationCommentTriviaSyntax)()?.ParentTrivia.Token.Parent
                _testSpeculativeNodeCallbackOpt?.Invoke(parentNode)
                If parentNode Is Nothing Then
                    Return
                End If
 
                Dim semanticModel = Await document.ReuseExistingSpeculativeModelAsync(parentNode, cancellationToken).ConfigureAwait(False)
 
                Dim symbols = GetSymbols(token, semanticModel, cancellationToken)
                If Not symbols.Any() Then
                    Return
                End If
 
                Dim text = Await document.GetValueTextAsync(cancellationToken).ConfigureAwait(False)
 
                Dim items = CreateCompletionItems(semanticModel, symbols, position)
                context.AddItems(items)
 
                If IsFirstCrefParameterContext(token) Then
                    ' Include Of in case they're typing a type parameter
                    context.AddItem(CreateOfCompletionItem())
                End If
 
                context.IsExclusive = True
            Catch e As Exception When FatalError.ReportAndCatchUnlessCanceled(e)
                ' nop
            End Try
        End Function
 
        Protected Overrides Async Function GetSymbolsAsync(document As Document, position As Integer, options As CompletionOptions, cancellationToken As CancellationToken) As Task(Of (SyntaxToken, SemanticModel, ImmutableArray(Of ISymbol)))
            Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
            Dim token = tree.GetTargetToken(position, cancellationToken)
 
            If IsCrefTypeParameterContext(token) Then
                Return Nothing
            End If
 
            ' To get a Speculative SemanticModel (which is much faster), we need to 
            ' walk up to the node the DocumentationTrivia is attached to.
            Dim parentNode = token.Parent?.FirstAncestorOrSelf(Of DocumentationCommentTriviaSyntax)()?.ParentTrivia.Token.Parent
            _testSpeculativeNodeCallbackOpt?.Invoke(parentNode)
            If parentNode Is Nothing Then
                Return Nothing
            End If
 
            Dim semanticModel = Await document.ReuseExistingSpeculativeModelAsync(parentNode, cancellationToken).ConfigureAwait(False)
 
            Dim symbols = GetSymbols(token, semanticModel, cancellationToken)
            Return (token, semanticModel, symbols.ToImmutableArray())
        End Function
 
        Private Shared Function IsCrefTypeParameterContext(token As SyntaxToken) As Boolean
            Return (token.IsChildToken(Function(t As TypeArgumentListSyntax) t.OfKeyword) OrElse
                token.IsChildSeparatorToken(Function(t As TypeArgumentListSyntax) t.Arguments)) AndAlso
                token.Parent?.FirstAncestorOrSelf(Of XmlCrefAttributeSyntax)() IsNot Nothing
        End Function
 
        Private Shared Function IsCrefStartContext(token As SyntaxToken) As Boolean
            ' cases:
            '   <see cref="x|
            '   <see cref='x|
            If token.IsChildToken(Function(x As XmlCrefAttributeSyntax) x.StartQuoteToken) Then
                Return True
            End If
 
            ' cases:
            '   <see cref="|
            '   <see cref='|
            If token.Parent.IsKind(SyntaxKind.XmlString) AndAlso token.Parent.IsParentKind(SyntaxKind.XmlAttribute) Then
                Dim xmlAttribute = DirectCast(token.Parent.Parent, XmlAttributeSyntax)
                Dim xmlName = TryCast(xmlAttribute.Name, XmlNameSyntax)
                Dim xmlValue = TryCast(xmlAttribute.Value, XmlStringSyntax)
 
                If xmlName?.LocalName.ValueText = "cref" AndAlso xmlValue?.StartQuoteToken = token Then
                    Return True
                End If
            End If
 
            Return False
        End Function
 
        Private Shared Function IsCrefParameterListContext(token As SyntaxToken) As Boolean
            ' cases:
            '   <see cref="M(|
            '   <see cref="M(x, |
            Return IsFirstCrefParameterContext(token) OrElse
                   token.IsChildSeparatorToken(Function(x As CrefSignatureSyntax) x.ArgumentTypes)
        End Function
 
        Private Shared Function IsFirstCrefParameterContext(ByRef token As SyntaxToken) As Boolean
            Return token.IsChildToken(Function(x As CrefSignatureSyntax) x.OpenParenToken)
        End Function
 
        Private Overloads Shared Function GetSymbols(token As SyntaxToken, semanticModel As SemanticModel, cancellationToken As CancellationToken) As IEnumerable(Of ISymbol)
            If IsCrefStartContext(token) Then
                Return semanticModel.LookupSymbols(token.SpanStart)
            ElseIf IsCrefParameterListContext(token) Then
                Return semanticModel.LookupNamespacesAndTypes(token.SpanStart)
            ElseIf token.IsChildToken(Function(x As QualifiedNameSyntax) x.DotToken) Then
                Return GetQualifiedSymbols(DirectCast(token.Parent, QualifiedNameSyntax), token, semanticModel, cancellationToken)
            End If
 
            Return SpecializedCollections.EmptyEnumerable(Of ISymbol)
        End Function
 
        Private Shared Iterator Function GetQualifiedSymbols(qualifiedName As QualifiedNameSyntax, token As SyntaxToken, semanticModel As SemanticModel, cancellationToken As CancellationToken) As IEnumerable(Of ISymbol)
            Dim leftSymbol = semanticModel.GetSymbolInfo(qualifiedName.Left, cancellationToken).Symbol
            Dim leftType = semanticModel.GetTypeInfo(qualifiedName.Left, cancellationToken).Type
 
            Dim container = TryCast(If(leftSymbol, leftType), INamespaceOrTypeSymbol)
 
            For Each symbol In semanticModel.LookupSymbols(token.SpanStart, container)
                Yield symbol
            Next
 
            Dim namedTypeContainer = TryCast(container, INamedTypeSymbol)
            If namedTypeContainer IsNot Nothing Then
                For Each constructor In namedTypeContainer.Constructors
                    If Not constructor.IsStatic Then
                        Yield constructor
                    End If
                Next
            End If
        End Function
 
        Private Shared Iterator Function CreateCompletionItems(
                semanticModel As SemanticModel,
                symbols As IEnumerable(Of ISymbol), position As Integer) As IEnumerable(Of CompletionItem)
 
            Dim builder = SharedPools.Default(Of StringBuilder).Allocate()
            Try
                For Each symbol In symbols
                    builder.Clear()
                    Yield CreateCompletionItem(semanticModel, symbol, position, builder)
                Next
            Finally
                SharedPools.Default(Of StringBuilder).ClearAndFree(builder)
            End Try
        End Function
 
        Private Shared Function CreateCompletionItem(
                semanticModel As SemanticModel,
                symbol As ISymbol, position As Integer, builder As StringBuilder) As CompletionItem
 
            If symbol.IsUserDefinedOperator() Then
                builder.Append("Operator ")
            End If
 
            builder.Append(symbol.ToDisplayString(s_crefFormat))
 
            Dim parameters = symbol.GetParameters()
 
            If Not parameters.IsDefaultOrEmpty Then
                builder.Append("("c)
 
                For i = 0 To parameters.Length - 1
                    If i > 0 Then
                        builder.Append(", ")
                    End If
 
                    Dim parameter = parameters(i)
 
                    If parameter.RefKind = RefKind.Ref Then
                        builder.Append("ByRef ")
                    End If
 
                    builder.Append(parameter.Type.ToMinimalDisplayString(semanticModel, position, s_minimalParameterTypeFormat))
                Next
 
                builder.Append(")"c)
            ElseIf symbol.Kind = SymbolKind.Method Then
                builder.Append("()")
            End If
 
            Dim displayString = builder.ToString()
 
            Return SymbolCompletionItem.CreateWithNameAndKind(
                displayText:=displayString,
                displayTextSuffix:="",
                insertionText:=Nothing,
                symbols:=ImmutableArray.Create(symbol),
                contextPosition:=position,
                rules:=GetRules(displayString))
        End Function
 
        Private Shared Function CreateOfCompletionItem() As CompletionItem
            Return CommonCompletionItem.Create(
                "Of", displayTextSuffix:="", CompletionItemRules.Default, Glyph.Keyword,
                description:=RecommendedKeyword.CreateDisplayParts("Of", VBFeaturesResources.Identifies_a_type_parameter_on_a_generic_class_structure_interface_delegate_or_procedure))
        End Function
 
        Private Shared ReadOnly s_WithoutOpenParen As CharacterSetModificationRule = CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, "("c)
        Private Shared ReadOnly s_WithoutSpace As CharacterSetModificationRule = CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, " "c)
 
        Private Shared ReadOnly s_defaultRules As CompletionItemRules = CompletionItemRules.Default
 
        Private Shared Function GetRules(displayText As String) As CompletionItemRules
            Dim commitRules = s_defaultRules.CommitCharacterRules
 
            If displayText.Contains("(") Then
                commitRules = commitRules.Add(s_WithoutOpenParen)
            End If
 
            Return s_defaultRules.WithCommitCharacterRules(commitRules)
        End Function
 
        Friend Function GetTestAccessor() As TestAccessor
            Return New TestAccessor(Me)
        End Function
 
        Friend Structure TestAccessor
            Private ReadOnly _crefCompletionProvider As CrefCompletionProvider
 
            Public Sub New(crefCompletionProvider As CrefCompletionProvider)
                _crefCompletionProvider = crefCompletionProvider
            End Sub
 
            Public Sub SetSpeculativeNodeCallback(value As Action(Of SyntaxNode))
                _crefCompletionProvider._testSpeculativeNodeCallbackOpt = value
            End Sub
        End Structure
    End Class
End Namespace