File: Text\TextSpan.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Runtime.Serialization;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Text
{
    /// <summary>
    /// Immutable abstract representation of a span of text.  For example, in an error diagnostic that reports a
    /// location, it could come from a parsed string, text from a tool editor buffer, etc.
    /// </summary>
    [DataContract]
    public readonly struct TextSpan : IEquatable<TextSpan>, IComparable<TextSpan>
    {
        /// <summary>
        /// Creates a TextSpan instance beginning with the position Start and having the Length
        /// specified with <paramref name="length" />.
        /// </summary>
        public TextSpan(int start, int length)
        {
            if (start < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(start));
            }
 
            if (start + length < start)
            {
                throw new ArgumentOutOfRangeException(nameof(length));
            }
 
            Start = start;
            Length = length;
        }
 
        /// <summary>
        /// Start point of the span.
        /// </summary>
        [DataMember(Order = 0)]
        public int Start { get; }
 
        /// <summary>
        /// End of the span.
        /// </summary>
        public int End => Start + Length;
 
        /// <summary>
        /// Length of the span.
        /// </summary>
        [DataMember(Order = 1)]
        public int Length { get; }
 
        /// <summary>
        /// Determines whether or not the span is empty.
        /// </summary>
        public bool IsEmpty => this.Length == 0;
 
        /// <summary>
        /// Determines whether the position lies within the span.
        /// </summary>
        /// <param name="position">
        /// The position to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if the position is greater than or equal to Start and strictly less 
        /// than End, otherwise <c>false</c>.
        /// </returns>
        public bool Contains(int position)
        {
            return unchecked((uint)(position - Start) < (uint)Length);
        }
 
        /// <summary>
        /// Determines whether <paramref name="span"/> falls completely within this span.
        /// </summary>
        /// <param name="span">
        /// The span to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if the specified span falls completely within this span, otherwise <c>false</c>.
        /// </returns>
        public bool Contains(TextSpan span)
        {
            return span.Start >= Start && span.End <= this.End;
        }
 
        /// <summary>
        /// Determines whether <paramref name="span"/> overlaps this span. Two spans are considered to overlap 
        /// if they have positions in common and neither is empty. Empty spans do not overlap with any 
        /// other span.
        /// </summary>
        /// <param name="span">
        /// The span to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if the spans overlap, otherwise <c>false</c>.
        /// </returns>
        public bool OverlapsWith(TextSpan span)
        {
            int overlapStart = Math.Max(Start, span.Start);
            int overlapEnd = Math.Min(this.End, span.End);
 
            return overlapStart < overlapEnd;
        }
 
        /// <summary>
        /// Returns the overlap with the given span, or null if there is no overlap.
        /// </summary>
        /// <param name="span">
        /// The span to check.
        /// </param>
        /// <returns>
        /// The overlap of the spans, or null if the overlap is empty.
        /// </returns>
        public TextSpan? Overlap(TextSpan span)
        {
            int overlapStart = Math.Max(Start, span.Start);
            int overlapEnd = Math.Min(this.End, span.End);
 
            return overlapStart < overlapEnd
                ? TextSpan.FromBounds(overlapStart, overlapEnd)
                : (TextSpan?)null;
        }
 
        /// <summary>
        /// Determines whether <paramref name="span"/> intersects this span. Two spans are considered to 
        /// intersect if they have positions in common or the end of one span 
        /// coincides with the start of the other span.
        /// </summary>
        /// <param name="span">
        /// The span to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if the spans intersect, otherwise <c>false</c>.
        /// </returns>
        public bool IntersectsWith(TextSpan span)
        {
            return span.Start <= this.End && span.End >= Start;
        }
 
        /// <summary>
        /// Determines whether <paramref name="position"/> intersects this span. 
        /// A position is considered to intersect if it is between the start and
        /// end positions (inclusive) of this span.
        /// </summary>
        /// <param name="position">
        /// The position to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if the position intersects, otherwise <c>false</c>.
        /// </returns>
        public bool IntersectsWith(int position)
        {
            return unchecked((uint)(position - Start) <= (uint)Length);
        }
 
        /// <summary>
        /// Returns the intersection with the given span, or null if there is no intersection.
        /// </summary>
        /// <param name="span">
        /// The span to check.
        /// </param>
        /// <returns>
        /// The intersection of the spans, or null if the intersection is empty.
        /// </returns>
        public TextSpan? Intersection(TextSpan span)
        {
            int intersectStart = Math.Max(Start, span.Start);
            int intersectEnd = Math.Min(this.End, span.End);
 
            return intersectStart <= intersectEnd
                ? TextSpan.FromBounds(intersectStart, intersectEnd)
                : (TextSpan?)null;
        }
 
        /// <summary>
        /// Creates a new <see cref="TextSpan"/> from <paramref name="start" /> and <paramref
        /// name="end"/> positions as opposed to a position and length.
        /// 
        /// The returned TextSpan contains the range with <paramref name="start"/> inclusive, 
        /// and <paramref name="end"/> exclusive.
        /// </summary>
        public static TextSpan FromBounds(int start, int end)
        {
            if (start < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(start), CodeAnalysisResources.StartMustNotBeNegative);
            }
 
            if (end < start)
            {
                throw new ArgumentOutOfRangeException(nameof(end), string.Format(CodeAnalysisResources.EndMustNotBeLessThanStart, start, end));
            }
 
            return new TextSpan(start, end - start);
        }
 
        /// <summary>
        /// Determines if two instances of <see cref="TextSpan"/> are the same.
        /// </summary>
        public static bool operator ==(TextSpan left, TextSpan right)
        {
            return left.Equals(right);
        }
 
        /// <summary>
        /// Determines if two instances of <see cref="TextSpan"/> are different.
        /// </summary>
        public static bool operator !=(TextSpan left, TextSpan right)
        {
            return !left.Equals(right);
        }
 
        /// <summary>
        /// Determines if current instance of <see cref="TextSpan"/> is equal to another.
        /// </summary>
        public bool Equals(TextSpan other)
        {
            return Start == other.Start && Length == other.Length;
        }
 
        /// <summary>
        /// Determines if current instance of <see cref="TextSpan"/> is equal to another.
        /// </summary>
        public override bool Equals(object? obj)
            => obj is TextSpan span && Equals(span);
 
        /// <summary>
        /// Produces a hash code for <see cref="TextSpan"/>.
        /// </summary>
        public override int GetHashCode()
        {
            return Hash.Combine(Start, Length);
        }
 
        /// <summary>
        /// Provides a string representation for <see cref="TextSpan"/>.
        /// This representation uses "half-open interval" notation, indicating the endpoint character is not included.
        /// Example: <c>[10..20)</c>, indicating the text starts at position 10 and ends at position 20 not included.
        /// </summary>
        public override string ToString()
        {
            return $"[{Start}..{End})";
        }
 
        /// <summary>
        /// Compares current instance of <see cref="TextSpan"/> with another.
        /// </summary>
        public int CompareTo(TextSpan other)
        {
            var diff = Start - other.Start;
            if (diff != 0)
            {
                return diff;
            }
 
            return Length - other.Length;
        }
    }
}