| File: Highlighting\KeywordHighlighters\ForLoopBlockHighlighter.vb | Web Access |
| Project: src\roslyn\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.Composition Imports System.Threading Imports Microsoft.CodeAnalysis.Highlighting Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.KeywordHighlighting <ExportHighlighter(LanguageNames.VisualBasic), [Shared]> Friend Class ForLoopBlockHighlighter Inherits AbstractKeywordHighlighter(Of SyntaxNode) <ImportingConstructor> <Obsolete(MefConstruction.ImportingConstructorMessage, True)> Public Sub New() End Sub Protected Overloads Overrides Sub AddHighlights(node As SyntaxNode, highlights As List(Of TextSpan), cancellationToken As CancellationToken) Dim forBlock = GetForBlockFromNode(node) If forBlock Is Nothing Then Return End If If TypeOf forBlock.ForOrForEachStatement Is ForStatementSyntax Then With DirectCast(forBlock.ForOrForEachStatement, ForStatementSyntax) highlights.Add(.ForKeyword.Span) highlights.Add(.ToKeyword.Span) If .StepClause IsNot Nothing Then highlights.Add(.StepClause.StepKeyword.Span) End If End With ElseIf TypeOf forBlock.ForOrForEachStatement Is ForEachStatementSyntax Then With DirectCast(forBlock.ForOrForEachStatement, ForEachStatementSyntax) highlights.Add(TextSpan.FromBounds(.ForKeyword.SpanStart, .EachKeyword.Span.End)) highlights.Add(.InKeyword.Span) End With Else Throw ExceptionUtilities.UnexpectedValue(forBlock.ForOrForEachStatement) End If highlights.AddRange( forBlock.GetRelatedStatementHighlights( blockKind:=SyntaxKind.ForKeyword)) Dim nextStatement = GetNextStatementMatchingForBlock(forBlock) If nextStatement IsNot Nothing Then highlights.Add(nextStatement.NextKeyword.Span) End If End Sub Private Shared Function GetForBlockFromNode(node As SyntaxNode) As ForOrForEachBlockSyntax If node.IsIncorrectContinueStatement(SyntaxKind.ContinueForStatement) OrElse node.IsIncorrectExitStatement(SyntaxKind.ExitForStatement) Then Return Nothing End If ' If cursor is in the Next statement, find the outermost For block logically associated ' with it because Next <identifier list> statements get tied to the innermost matching ' For block but we want to highlight the outermost matching For block. If TypeOf node Is NextStatementSyntax Then ' If there is no For block the correct number of levels out (consider 2 nested For ' blocks terminated by "Next c, b, a"), then choose the outermost even if it's not ' the exact match. Return GetForBlocksMatchingNextStatement(DirectCast(node, NextStatementSyntax)).FirstOrDefault() Else Return node.AncestorsAndSelf().OfType(Of ForOrForEachBlockSyntax)().FirstOrDefault() End If End Function ''' <summary> ''' Find the Next statement that closes this For block (if one exists). Normally that ''' would just be the one associated with forBlock, but if we have a "Next a, b" ''' statement that is closing multiple loops, the Next statement is attached to the ''' innermost matching loop. ''' </summary> Private Shared Function GetNextStatementMatchingForBlock(forBlock As ForOrForEachBlockSyntax) As NextStatementSyntax Dim forBlockChild = forBlock While forBlockChild IsNot Nothing If forBlockChild.NextStatement IsNot Nothing Then ' Once we find a candidate Next statement, it must either be the one that ' closes our For block or there must not exist a matching Next statement ' so give up the search. If a more deeply nested For block had the Next ' statement which closes our For block, then that Next statement would ' have been associated with the For block we're currently inspecting. ' Check to see whether the Next statement found closes enough For blocks If GetForBlocksMatchingNextStatement(forBlockChild.NextStatement).Contains(forBlock) Then Return forBlockChild.NextStatement Else Return Nothing End If End If ' Choose the last immediate child For block at each level. Any earlier For blocks ' in the set of children cannot possibly close this For block because if it did ' then IT would be the last For block instead. Similarly, if we find a Next ' statement in this manner that closes sufficiently many For blocks then there ' can be nothing between the end of that For block and the end of our For block. forBlockChild = forBlockChild.ChildNodes().OfType(Of ForOrForEachBlockSyntax).LastOrDefault() End While Return Nothing End Function ''' <summary> ''' Returns all For blocks logically matching the Next statement, ordered from outermost to ''' innermost. Do not consider the actual names of the loop variables because highlighting ''' should work even if the wrong identifier names are listed. ''' </summary> Private Shared Function GetForBlocksMatchingNextStatement(nextStatement As NextStatementSyntax) As IEnumerable(Of ForOrForEachBlockSyntax) ' If there are 0 control variables, then one for block is closed by the Next statement Dim numExpectedForBlocksMatched = Math.Max(nextStatement.ControlVariables.Count(), 1) Return nextStatement.GetAncestors(Of ForOrForEachBlockSyntax).Take(numExpectedForBlocksMatched).Reverse() End Function End Class End Namespace