File: Shared\Extensions\IBufferGraphExtensions.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.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Projection;
 
namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions;
 
internal enum BufferMapDirection
{
    Identity,
    Down,
    Up,
    Unrelated
}
 
internal static class IBufferGraphExtensions
{
    public static SnapshotSpan? MapUpOrDownToFirstMatch(this IBufferGraph bufferGraph, SnapshotSpan span, Predicate<ITextSnapshot> match)
    {
        var spans = bufferGraph.MapDownToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
        if (spans.Count == 0)
        {
            spans = bufferGraph.MapUpToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
        }
 
        return spans.Count > 0 ? spans[0] : null;
    }
 
    public static SnapshotSpan? MapUpOrDownToBuffer(this IBufferGraph bufferGraph, SnapshotSpan span, ITextBuffer targetBuffer)
    {
        var direction = ClassifyBufferMapDirection(span.Snapshot.TextBuffer, targetBuffer);
        switch (direction)
        {
            case BufferMapDirection.Identity:
                return span;
 
            case BufferMapDirection.Down:
                {
                    var spans = bufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeExclusive, targetBuffer);
                    return spans.Count > 0 ? spans[0] : null;
                }
 
            case BufferMapDirection.Up:
                {
                    var spans = bufferGraph.MapUpToBuffer(span, SpanTrackingMode.EdgeExclusive, targetBuffer);
                    return spans.Count > 0 ? spans[0] : null;
                }
 
            default:
                return null;
        }
    }
 
    public static SnapshotPoint? MapUpOrDownToBuffer(this IBufferGraph bufferGraph, SnapshotPoint point, ITextBuffer targetBuffer)
    {
        var direction = ClassifyBufferMapDirection(point.Snapshot.TextBuffer, targetBuffer);
        switch (direction)
        {
            case BufferMapDirection.Identity:
                return point;
 
            case BufferMapDirection.Down:
                {
                    // TODO (https://github.com/dotnet/roslyn/issues/5281): Remove try-catch.
                    try
                    {
                        return bufferGraph.MapDownToInsertionPoint(point, PointTrackingMode.Positive, s => s == targetBuffer.CurrentSnapshot);
                    }
                    catch (ArgumentOutOfRangeException) when (bufferGraph.TopBuffer.ContentType.TypeName == "Interactive Content")
                    {
                        // Suppress this to work around DevDiv #144964.
                        // Note: Other callers might be affected, but this is the narrowest workaround for the observed problems.
                        // A fix is already being reviewed, so a broader change is not required.
                        return null;
                    }
                }
 
            case BufferMapDirection.Up:
                {
                    return bufferGraph.MapUpToBuffer(point, PointTrackingMode.Positive, PositionAffinity.Predecessor, targetBuffer);
                }
 
            default:
                return null;
        }
    }
 
    public static BufferMapDirection ClassifyBufferMapDirection(ITextBuffer startBuffer, ITextBuffer destinationBuffer)
    {
        if (startBuffer == destinationBuffer)
        {
            return BufferMapDirection.Identity;
        }
 
        // Are we trying to map down or up?
        if (startBuffer is IProjectionBufferBase startProjBuffer && IsSourceBuffer(startProjBuffer, destinationBuffer))
        {
            return BufferMapDirection.Down;
        }
 
        if (destinationBuffer is IProjectionBufferBase destProjBuffer && IsSourceBuffer(destProjBuffer, startBuffer))
        {
            return BufferMapDirection.Up;
        }
 
        return BufferMapDirection.Unrelated;
    }
 
    private static bool IsSourceBuffer(IProjectionBufferBase top, ITextBuffer bottom)
    {
        return top.SourceBuffers.Contains(bottom) ||
            top.SourceBuffers.OfType<IProjectionBufferBase>().Any(b => IsSourceBuffer(b, bottom));
    }
}