|
' 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.Providers
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.KeywordRecommenders.Declarations
''' <summary>
''' Recommends "End [block]" or, if after a End keyword, just the Block.
''' </summary>
Friend Class EndBlockKeywordRecommender
Inherits AbstractKeywordRecommender
Protected Overrides Function RecommendKeywords(context As VisualBasicSyntaxContext, cancellationToken As CancellationToken) As ImmutableArray(Of RecommendedKeyword)
If context.IsPreProcessorDirectiveContext Then
Return ImmutableArray(Of RecommendedKeyword).Empty
End If
Dim targetToken = context.TargetToken
If targetToken.IsKind(SyntaxKind.EndKeyword) AndAlso
(targetToken.IsChildToken(Of EndBlockStatementSyntax)(Function(endBlock) endBlock.EndKeyword) OrElse
targetToken.IsChildToken(Of StopOrEndStatementSyntax)(Function(endStatement) endStatement.StopOrEndKeyword)) Then
' We already have "End", so just recommend the closeable things
' NOTE: use the token to the left of "End" to locate parenting blocks, otherwise, this won't work
' for blocks that can't contain an End statement. For example, Enums and Interfaces -- in these cases
' the End statement is located outside of the block.
targetToken = targetToken.GetPreviousToken()
Dim keywords = From keyword In GetUnclosedBlockKeywords(targetToken.Parent)
Select SyntaxFacts.GetText(keyword)
Dim keywordList = keywords.ToList()
EnsureAllIfAny(keywordList, "Function", "Sub")
Return keywordList.SelectAsArray(Function(k) New RecommendedKeyword(k, GetToolTipForKeyword(k)))
ElseIf context.FollowsEndOfStatement Then
' If you're in a case like this
'
' End If
' |
'
' our target token is the "If", even though it's closed. So we want to skip to the parent of the If
' block to start to figure out what blocks we still have
Dim node = targetToken.Parent
If TypeOf node Is EndBlockStatementSyntax Then
node = node.Parent.Parent
End If
If node IsNot Nothing Then
' We don't have "End", so recommend everything with the End keyword
Return GetUnclosedBlockKeywords(node).SelectAsArray(
Function(k) New RecommendedKeyword("End " & SyntaxFacts.GetText(k), GetToolTipForKeyword(SyntaxFacts.GetText(k))))
End If
End If
Return ImmutableArray(Of RecommendedKeyword).Empty
End Function
Private Shared Function GetToolTipForKeyword(keyword As String) As String
Select Case keyword
Case "Region", "Class", "Structure", "Namespace", "Module"
Return String.Format(VBFeaturesResources.Terminates_a_0_block, keyword)
Case "Interface", "Enum"
Return String.Format(VBFeaturesResources.Terminates_an_0_block, keyword)
Case "Select"
Return String.Format(VBFeaturesResources.Terminates_the_definition_of_a_0_statement, keyword & " Case")
Case "SyncLock", "Try", "Using", "While", "With", "Sub", "Function", "Set", "Get", "RemoveHandler", "RaiseEvent"
Return String.Format(VBFeaturesResources.Terminates_the_definition_of_a_0_statement, keyword)
Case "If", "Operator", "AddHandler"
Return String.Format(VBFeaturesResources.Terminates_the_definition_of_an_0_statement, keyword)
Case Else
Return String.Empty
End Select
End Function
Private Shared Sub EnsureAllIfAny(collection As ICollection(Of String), ParamArray completions() As String)
For Each item In completions
If collection.Contains(item) Then
For Each item2 In completions
If Not collection.Contains(item2) Then
collection.Add(item2)
End If
Next
Exit For
End If
Next
End Sub
Private Shared Function GetUnclosedBlockKeywords(node As SyntaxNode) As IEnumerable(Of SyntaxKind)
Dim visitor As New MissingKeywordExtractor()
Return From ancestor In node.GetAncestorsOrThis(Of SyntaxNode)()
Select missingKeyword = visitor.Visit(ancestor)
Where missingKeyword.HasValue
Select missingKeyword.Value
Distinct
End Function
Private Class MissingKeywordExtractor
Inherits VisualBasicSyntaxVisitor(Of SyntaxKind?)
Public Overrides Function VisitNamespaceBlock(node As NamespaceBlockSyntax) As SyntaxKind?
If node.EndNamespaceStatement.IsMissing Then
Return SyntaxKind.NamespaceKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitModuleBlock(ByVal node As ModuleBlockSyntax) As SyntaxKind?
If node.EndBlockStatement.IsMissing Then
Return SyntaxKind.ModuleKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitClassBlock(ByVal node As ClassBlockSyntax) As SyntaxKind?
If node.EndBlockStatement.IsMissing Then
Return SyntaxKind.ClassKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitStructureBlock(ByVal node As StructureBlockSyntax) As SyntaxKind?
If node.EndBlockStatement.IsMissing Then
Return SyntaxKind.StructureKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitInterfaceBlock(ByVal node As InterfaceBlockSyntax) As SyntaxKind?
If node.EndBlockStatement.IsMissing Then
Return SyntaxKind.InterfaceKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitEnumBlock(node As EnumBlockSyntax) As SyntaxKind?
If node.EndEnumStatement.IsMissing Then
Return SyntaxKind.EnumKeyword
Else
Return Nothing
End If
End Function
Private Shared Function VisitMethodBlockBase(node As MethodBlockBaseSyntax) As SyntaxKind?
If node.EndBlockStatement.IsMissing Then
Return node.BlockStatement.DeclarationKeyword.Kind
Else
Return Nothing
End If
End Function
Public Overrides Function VisitMethodBlock(node As MethodBlockSyntax) As SyntaxKind?
Return VisitMethodBlockBase(node)
End Function
Public Overrides Function VisitConstructorBlock(node As ConstructorBlockSyntax) As SyntaxKind?
Return VisitMethodBlockBase(node)
End Function
Public Overrides Function VisitOperatorBlock(node As OperatorBlockSyntax) As SyntaxKind?
Return VisitMethodBlockBase(node)
End Function
Public Overrides Function VisitAccessorBlock(node As AccessorBlockSyntax) As SyntaxKind?
Return VisitMethodBlockBase(node)
End Function
Public Overrides Function VisitMultiLineIfBlock(node As MultiLineIfBlockSyntax) As SyntaxKind?
If node.EndIfStatement.IsMissing Then
Return SyntaxKind.IfKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitPropertyBlock(node As PropertyBlockSyntax) As SyntaxKind?
If node.EndPropertyStatement.IsMissing Then
Return node.PropertyStatement.DeclarationKeyword.Kind
Else
Return Nothing
End If
End Function
Public Overrides Function VisitSyncLockBlock(node As SyncLockBlockSyntax) As SyntaxKind?
If node.EndSyncLockStatement.IsMissing Then
Return SyntaxKind.SyncLockKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitSelectBlock(node As SelectBlockSyntax) As SyntaxKind?
If node.EndSelectStatement.IsMissing Then
Return SyntaxKind.SelectKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitUsingBlock(node As UsingBlockSyntax) As SyntaxKind?
If node.EndUsingStatement.IsMissing Then
Return SyntaxKind.UsingKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitWhileBlock(node As WhileBlockSyntax) As SyntaxKind?
If node.EndWhileStatement.IsMissing Then
Return SyntaxKind.WhileKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitWithBlock(node As WithBlockSyntax) As SyntaxKind?
If node.EndWithStatement.IsMissing Then
Return SyntaxKind.WithKeyword
Else
Return Nothing
End If
End Function
Public Overrides Function VisitTryBlock(node As TryBlockSyntax) As SyntaxKind?
If node.EndTryStatement.IsMissing Then
Return SyntaxKind.TryKeyword
Else
Return Nothing
End If
End Function
End Class
End Class
End Namespace
|