File: Shared\Extensions\FileLinePositionSpanExtensions.cs
Web Access
Project: ..\..\..\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);
        }
    }
}