File: Shared\Extensions\FileLinePositionSpanExtensions.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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 Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static class FileLinePositionSpanExtensions
{
    /// <inheritdoc cref="LinePositionSpanExtensions.GetClampedTextSpan"/>
    public static TextSpan GetClampedTextSpan(this FileLinePositionSpan span, SourceText text)
        => span.Span.GetClampedTextSpan(text);
 
    /// <inheritdoc cref="LinePositionSpanExtensions.GetClampedSpan"/>
    public static LinePositionSpan GetClampedSpan(this FileLinePositionSpan span, SourceText text)
        => span.Span.GetClampedSpan(text);
}
 
internal static class LinePositionSpanExtensions
{
    /// <summary>
    /// Returns a new <see cref="TextSpan"/> based off of the positions in <paramref name="span"/>, but
    /// which is guaranteed to fall entirely within the span of <paramref name="text"/>.
    /// </summary>
    public static TextSpan GetClampedTextSpan(this LinePositionSpan span, SourceText text)
    {
        var clampedSpan = span.GetClampedSpan(text);
        return text.Lines.GetTextSpan(clampedSpan);
    }
 
    /// <summary>
    /// Returns a new <see cref="LinePositionSpan"/> based off of the positions in <paramref name="span"/>, but
    /// which is guaranteed to fall entirely within the span of <paramref name="text"/>.
    /// </summary>
    public static LinePositionSpan GetClampedSpan(this LinePositionSpan span, SourceText text)
    {
        var lines = text.Lines;
        if (lines.Count == 0)
            return default;
 
        var startLine = span.Start.Line;
        var endLine = span.End.Line;
 
        // Make sure the starting columns are never negative.
        var startColumn = Math.Max(span.Start.Character, 0);
        var endColumn = Math.Max(span.End.Character, 0);
 
        if (startLine < 0)
        {
            // If the start line is negative (e.g. before the start of the actual document) then move the start to the 0,0 position.
            startLine = 0;
            startColumn = 0;
        }
        else if (startLine >= lines.Count)
        {
            // if the start line is after the end of the document, move the start to the last location in the document.
            startLine = lines.Count - 1;
            startColumn = lines[startLine].SpanIncludingLineBreak.Length;
        }
 
        if (endLine < 0)
        {
            // if the end is before the start of the document, then move the end to wherever the start position was determined to be.
            endLine = startLine;
            endColumn = startColumn;
        }
        else if (endLine >= lines.Count)
        {
            // if the end line is after the end of the document, move the end to the last location in the document.
            endLine = lines.Count - 1;
            endColumn = lines[endLine].SpanIncludingLineBreak.Length;
        }
 
        // now, ensure that the column of the start/end positions is within the length of its line.
        startColumn = Math.Min(startColumn, lines[startLine].SpanIncludingLineBreak.Length);
        endColumn = Math.Min(endColumn, lines[endLine].SpanIncludingLineBreak.Length);
 
        var start = new LinePosition(startLine, startColumn);
        var end = new LinePosition(endLine, endColumn);
 
        // swap if necessary
        if (end < start)
            (start, end) = (end, start);
 
        // Validate that the points are actually within the span of the text.
        Contract.ThrowIfTrue(start < text.Lines.GetLinePosition(0));
        Contract.ThrowIfTrue(end > text.Lines.GetLinePosition(text.Length));
        return new LinePositionSpan(start, end);
    }
}