File: LineSeparators\VisualBasicLineSeparatorService.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.Threading
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.LineSeparators
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineSeparators
    <ExportLanguageService(GetType(ILineSeparatorService), LanguageNames.VisualBasic), [Shared]>
    Friend Class VisualBasicLineSeparatorService
        Implements ILineSeparatorService
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        ''' <summary>Node types that are interesting for line separation.</summary>
        Private Shared Function IsSeparableBlock(nodeOrToken As SyntaxNodeOrToken) As Boolean
            If nodeOrToken.IsToken Then
                Return False
            End If
 
            Dim node = nodeOrToken.AsNode()
            Return _
                TypeOf (node) Is MethodBlockBaseSyntax OrElse
                TypeOf (node) Is PropertyBlockSyntax OrElse
                TypeOf (node) Is TypeBlockSyntax OrElse
                TypeOf (node) Is EnumBlockSyntax OrElse
                TypeOf (node) Is NamespaceBlockSyntax OrElse
                TypeOf (node) Is EventBlockSyntax
        End Function
 
        ''' <summary>Node types that may contain separable blocks.</summary>
        Private Function IsSeparableContainer(node As SyntaxNode) As Boolean
            Return _
                TypeOf node Is TypeBlockSyntax OrElse
                TypeOf node Is EnumBlockSyntax OrElse
                TypeOf node Is NamespaceBlockSyntax OrElse
                TypeOf node Is CompilationUnitSyntax
        End Function
 
        ''' <summary>
        ''' Given a syntaxTree returns line separator spans. The operation may take fairly long time
        ''' on a big syntaxTree so it is cancellable.
        ''' </summary>
        Public Async Function GetLineSeparatorsAsync(
                document As Document,
                textSpan As TextSpan,
                cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of TextSpan)) Implements ILineSeparatorService.GetLineSeparatorsAsync
            Dim syntaxTree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
            Dim root = Await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(False)
 
            Dim spans = ArrayBuilder(Of TextSpan).GetInstance()
 
            Dim blocks = root.Traverse(Of SyntaxNode)(textSpan, AddressOf IsSeparableContainer)
 
            For Each block In blocks
                If cancellationToken.IsCancellationRequested Then
                    Return ImmutableArray(Of TextSpan).Empty
                End If
 
                Dim typeBlock = TryCast(block, TypeBlockSyntax)
                If typeBlock IsNot Nothing Then
                    ProcessNodeList(typeBlock.Members, spans, cancellationToken)
                    Continue For
                End If
 
                Dim enumBlock = TryCast(block, EnumBlockSyntax)
                If enumBlock IsNot Nothing Then
                    ProcessNodeList(enumBlock.Members, spans, cancellationToken)
                    Continue For
                End If
 
                Dim nsBlock = TryCast(block, NamespaceBlockSyntax)
                If nsBlock IsNot Nothing Then
                    ProcessNodeList(nsBlock.Members, spans, cancellationToken)
                    Continue For
                End If
 
                Dim progBlock = TryCast(block, CompilationUnitSyntax)
                If progBlock IsNot Nothing Then
                    ProcessImports(progBlock.Imports, spans)
                    ProcessNodeList(progBlock.Members, spans, cancellationToken)
                End If
            Next
 
            Return spans.ToImmutable()
        End Function
 
        ''' <summary>
        ''' If node is separable and not the last in its container => add line separator after the node
        ''' If node is separable and not the first in its container => ensure separator before the node
        ''' last separable node in Program needs separator after it.
        ''' </summary>
        Private Shared Sub ProcessNodeList(Of T As SyntaxNode)(children As SyntaxList(Of T), spans As ArrayBuilder(Of TextSpan), token As CancellationToken)
            Contract.ThrowIfNull(spans)
 
            If children.Count = 0 Then
                Return ' nothing to separate
            End If
 
            Dim seenSeparator As Boolean = True 'first child needs no separator
            For i As Integer = 0 To children.Count - 2
                token.ThrowIfCancellationRequested()
 
                Dim cur = children(i)
                If Not IsSeparableBlock(cur) Then
                    seenSeparator = False
                Else
                    If Not seenSeparator Then
                        Dim prev = children(i - 1)
                        spans.Add(GetLineSeparatorSpanForNode(prev))
                    End If
 
                    spans.Add(GetLineSeparatorSpanForNode(cur))
 
                    seenSeparator = True
                End If
            Next
 
            ' last child may need separator only before it
            Dim lastChild = children.Last()
 
            If IsSeparableBlock(lastChild) Then
                If Not seenSeparator Then
                    Dim nextToLast = children(children.Count - 2)
                    spans.Add(GetLineSeparatorSpanForNode(nextToLast))
                End If
 
                If lastChild.Parent.Kind() = SyntaxKind.CompilationUnit Then
                    spans.Add(GetLineSeparatorSpanForNode(lastChild))
                End If
            End If
 
        End Sub
 
        Private Shared Sub ProcessImports(importsList As SyntaxList(Of ImportsStatementSyntax), spans As ArrayBuilder(Of TextSpan))
            If importsList.Any() Then
                spans.Add(GetLineSeparatorSpanForNode(importsList.Last()))
            End If
        End Sub
 
        Private Shared Function GetLineSeparatorSpanForNode(node As SyntaxNode) As TextSpan
            Contract.ThrowIfNull(node)
 
            ' PERF: Reverse the list to only realize the last child
            Dim lastToken = node.ChildNodesAndTokens().Reverse().First()
            Return lastToken.Span
        End Function
    End Class
End Namespace