| File: LineSeparators\VisualBasicLineSeparatorService.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.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