File: Structure\StructureTag.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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 Microsoft.CodeAnalysis.Structure;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.Structure;
 
#pragma warning disable CS0618 // Type or member is obsolete
internal sealed class StructureTag(AbstractStructureTaggerProvider tagProvider, BlockSpan blockSpan, ITextSnapshot snapshot)
    : IContainerStructureTag, IEquatable<StructureTag>
#pragma warning restore CS0618 // Type or member is obsolete
{
    private readonly AbstractStructureTaggerProvider _tagProvider = tagProvider;
 
    public BlockSpan BlockSpan { get; } = blockSpan;
 
    /// <summary>
    /// The contents of the buffer to show if we mouse over the collapsed indicator.
    /// </summary>
    public readonly Span CollapsedHintFormSpan = blockSpan.HintSpan.ToSpan();
 
    public readonly string CollapsedText = blockSpan.BannerText;
 
    public ITextSnapshot Snapshot { get; } = snapshot;
 
    public Span OutliningSpan { get; } = blockSpan.TextSpan.ToSpan();
    public Span HeaderSpan { get; } = DetermineHeaderSpan(blockSpan.TextSpan, blockSpan.HintSpan, snapshot);
 
    Span? IStructureTag.OutliningSpan => OutliningSpan;
    Span? IStructureTag.HeaderSpan => HeaderSpan;
 
    public IReadOnlyList<SubHeadingStructureData>? SubHeadings { get; } = blockSpan.SubHeadings.IsDefault
        ? null
        : blockSpan.SubHeadings.SelectAsArray(CreateSubHeading, snapshot);
 
    public Span? GuideLineSpan => null;
    public int? GuideLineHorizontalAnchorPoint => null;
    public string Type { get; } = ConvertType(blockSpan.Type);
    public bool IsCollapsible { get; } = blockSpan.IsCollapsible;
    public bool IsDefaultCollapsed { get; } = blockSpan.IsDefaultCollapsed;
    public bool IsImplementation { get; } = blockSpan.AutoCollapse;
 
    private static SubHeadingStructureData CreateSubHeading((TextSpan textSpan, TextSpan hintSpan, string type) subHeading, ITextSnapshot snapshot)
        => new(subHeading.textSpan.ToSpan(), DetermineHeaderSpan(subHeading.textSpan, subHeading.hintSpan, snapshot), ConvertType(subHeading.type));
 
    private static Span DetermineHeaderSpan(TextSpan textSpan, TextSpan hintSpan, ITextSnapshot snapshot)
    {
        if (hintSpan.Start < textSpan.Start)
        {
            // The HeaderSpan is what is used for drawing the guidelines and also what is shown if you mouse over a
            // guideline. We will use the text from the hint start to the collapsing start; in the case this spans
            // multiple lines the editor will clip it for us and suffix an ellipsis at the end.
            return Span.FromBounds(hintSpan.Start, textSpan.Start);
        }
        else
        {
            var hintLine = snapshot.GetLineFromPosition(hintSpan.Start);
            return AbstractStructureTaggerProvider.TrimLeadingWhitespace(hintLine.Extent);
        }
    }
 
    // Editor uses this here:
    // https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_git/VS-Platform?path=/src/Editor/Text/Impl/Structure/StructureSpanningTree/StructureSpanningTree.cs&version=GBmain&line=308&lineEnd=309&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents
    public override int GetHashCode()
        => Hash.Combine(this.GuideLineHorizontalAnchorPoint.GetHashCode(),
           Hash.Combine(this.Type,
           Hash.Combine(this.IsCollapsible,
           Hash.Combine(this.IsDefaultCollapsed,
           Hash.Combine(this.IsImplementation,
           Hash.Combine(this.OutliningSpan.GetHashCode(),
           Hash.Combine(this.HeaderSpan.GetHashCode(),
           Hash.Combine(this.SubHeadings?.Count ?? 0, this.GuideLineSpan.GetHashCode()))))))));
 
    public override bool Equals(object? obj)
        => Equals(obj as StructureTag);
 
    public bool Equals(StructureTag? other)
    {
        if (other != null &&
            this.GuideLineHorizontalAnchorPoint == other.GuideLineHorizontalAnchorPoint &&
            this.Type == other.Type &&
            this.IsCollapsible == other.IsCollapsible &&
            this.IsDefaultCollapsed == other.IsDefaultCollapsed &&
            this.IsImplementation == other.IsImplementation &&
            _tagProvider.SpanEquals(this.Snapshot, this.OutliningSpan, other.Snapshot, other.OutliningSpan) &&
            _tagProvider.SpanEquals(this.Snapshot, this.HeaderSpan, other.Snapshot, other.HeaderSpan) &&
            _tagProvider.SpanEquals(this.Snapshot, this.GuideLineSpan, other.Snapshot, other.GuideLineSpan))
        {
            if (this.SubHeadings is null != other.SubHeadings is null)
                return false;
 
            if (this.SubHeadings is not null && other.SubHeadings is not null)
            {
                if (this.SubHeadings.Count != other.SubHeadings.Count)
                    return false;
 
                for (int i = 0, n = this.SubHeadings.Count; i < n; i++)
                {
                    var thisSubHeading = this.SubHeadings[i];
                    var otherSubHeading = other.SubHeadings[i];
 
                    if (thisSubHeading.Type != otherSubHeading.Type ||
                        !_tagProvider.SpanEquals(this.Snapshot, thisSubHeading.Span, other.Snapshot, otherSubHeading.Span) ||
                        !_tagProvider.SpanEquals(this.Snapshot, thisSubHeading.HeaderSpan, other.Snapshot, otherSubHeading.HeaderSpan))
                    {
                        return false;
                    }
                }
            }
 
            return true;
        }
 
        return false;
    }
 
    public object? GetCollapsedForm()
    {
        return CollapsedText;
    }
 
    public object? GetCollapsedHintForm()
    {
        return _tagProvider.GetCollapsedHintForm(this);
    }
 
    private static string ConvertType(string type)
        => type switch
        {
            BlockTypes.Conditional => PredefinedStructureTagTypes.Conditional,
            BlockTypes.Comment => PredefinedStructureTagTypes.Comment,
            BlockTypes.Expression => PredefinedStructureTagTypes.Expression,
            BlockTypes.Imports => PredefinedStructureTagTypes.Imports,
            BlockTypes.Loop => PredefinedStructureTagTypes.Loop,
            BlockTypes.Member => PredefinedStructureTagTypes.Member,
            BlockTypes.Namespace => PredefinedStructureTagTypes.Namespace,
            BlockTypes.Nonstructural => PredefinedStructureTagTypes.Nonstructural,
            BlockTypes.PreprocessorRegion => PredefinedStructureTagTypes.PreprocessorRegion,
            BlockTypes.Statement => PredefinedStructureTagTypes.Statement,
            BlockTypes.Type => PredefinedStructureTagTypes.Type,
            _ => PredefinedStructureTagTypes.Structural
        };
}