File: Structure\Syntax\AbstractBlockStructureProvider.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Structure;
 
/// <summary>
/// Note: this type is for subclassing by the VB and C# provider only.
/// It presumes that the language supports Syntax Trees.
/// </summary>
internal abstract class AbstractBlockStructureProvider : BlockStructureProvider
{
    private static readonly IComparer<BlockSpan> s_blockSpanComparer = Comparer<BlockSpan>.Create(static (x, y) => y.TextSpan.Start.CompareTo(x.TextSpan.Start));
 
    private readonly ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxStructureProvider>> _nodeProviderMap;
    private readonly ImmutableDictionary<int, ImmutableArray<AbstractSyntaxStructureProvider>> _triviaProviderMap;
 
    protected AbstractBlockStructureProvider(
        ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxStructureProvider>> defaultNodeOutlinerMap,
        ImmutableDictionary<int, ImmutableArray<AbstractSyntaxStructureProvider>> defaultTriviaOutlinerMap)
    {
        _nodeProviderMap = defaultNodeOutlinerMap;
        _triviaProviderMap = defaultTriviaOutlinerMap;
    }
 
    public override void ProvideBlockStructure(in BlockStructureContext context)
    {
        try
        {
            var syntaxRoot = context.SyntaxTree.GetRoot(context.CancellationToken);
            var initialContextCount = context.Spans.Count;
            BlockSpanCollector.CollectBlockSpans(
                syntaxRoot, context.Options, _nodeProviderMap, _triviaProviderMap, context.Spans, context.CancellationToken);
 
            // Sort descending, and keep track of the "last added line".
            // Then, ignore if we found a span on the same line.
            // The effect for this is if we have something like:
            //
            // M1(M2(
            //     ...
            //     ...
            // )
            //
            // We only collapse the "inner" span which has larger start.
            context.Spans.Sort(initialContextCount, s_blockSpanComparer);
            var text = context.SyntaxTree.GetText(context.CancellationToken);
            BlockSpan? lastSpan = null;
 
            context.Spans.RemoveWhere((span, index, _) =>
                {
                    // do not remove items before the first item that we added
                    if (index < initialContextCount)
                        return false;
 
                    if (span.IsOverlappingBlockSpan(text.Lines, lastSpan))
                        return true;
 
                    lastSpan = span;
                    return false;
                },
                arg: default(VoidResult));
        }
        catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
}