// // ISegment.cs // // Author: // Mike Krüger // // Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; namespace MonoDevelop.Core.Text { /// /// An (Offset,Length)-pair. /// public interface ISegment { /// /// Gets the start offset of the segment. /// int Offset { get; } /// /// Gets the length of the segment. /// /// For line segments (IDocumentLine), the length does not include the line delimeter. int Length { get; } /// /// Gets the end offset of the segment. /// /// EndOffset = Offset + Length; int EndOffset { get; } } /// /// An (Offset, Length) pair representing a text span. /// public struct TextSegment : IEquatable, ISegment { public static readonly TextSegment Invalid = new TextSegment (-1, 0); readonly int offset; /// /// Gets the start offset of the segment. /// /// /// The offset. /// public int Offset { get { return offset; } } readonly int length; /// /// Gets the length of the segment. /// /// /// The length. /// public int Length { get { return length; } } /// /// Gets the end offset of the segment. /// /// /// EndOffset = Offset + Length; /// /// /// The end offset. /// public int EndOffset { get { return Offset + Length; } } /// /// Gets a value indicating whether this instance is empty. /// /// /// true if this instance is empty; otherwise, false. /// public bool IsEmpty { get { return Length == 0; } } /// /// Gets a value indicating whether this instance is invalid. /// /// /// true if this instance is invalid; otherwise, false. /// public bool IsInvalid { get { return Offset < 0; } } /// /// Initializes a new instance of the struct. /// /// /// The offset of the segment. /// /// /// The length of the segment. /// public TextSegment (int offset, int length) { this.offset = offset; this.length = length; } public static bool operator == (TextSegment left, TextSegment right) { return Equals (left, right); } public static bool operator != (TextSegment left, TextSegment right) { return !Equals (left, right); } public static bool Equals (TextSegment left, TextSegment right) { return left.Offset == right.Offset && left.Length == right.Length; } /// /// Determines whether this instance is inside the specified offset. /// /// /// true if this instance is inside the specified offset (upper bound inclusive); otherwise, false. /// /// /// The offset offset. /// public bool IsInside (int offset) { return Offset <= offset && offset <= EndOffset; } /// /// Determines whether the specified is equal to the current . /// /// /// The to compare with the current . /// /// /// true if the specified is equal to the current /// ; otherwise, false. /// public bool Equals (TextSegment other) { return Equals (this, other); } /// /// Determines whether the specified is equal to the current . /// /// /// The to compare with the current . /// /// /// true if the specified is equal to the current /// ; otherwise, false. /// public override bool Equals (object obj) { return obj is ISegment && Equals (this, (ISegment)obj); } /// /// Serves as a hash function for a object. /// /// /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. /// public override int GetHashCode () { return Offset ^ Length; } public static TextSegment FromBounds (int startOffset, int endOffset) { if (startOffset > endOffset) throw new ArgumentOutOfRangeException ("endOffset", "endOffset < startOffset"); return new TextSegment (startOffset, endOffset - startOffset); } /// /// Returns a that represents the current . /// /// /// A that represents the current . /// public override string ToString () { return string.Format ("[TextSegment: Offset={0}, Length={1}]", Offset, Length); } } /// /// An abstract implementation of the ISegment (Offset, Length) pair representing a text span. /// public abstract class AbstractSegment : ISegment { readonly int offset; /// /// Gets the start offset of the segment. /// /// /// The offset. /// public int Offset { get { return offset; } } readonly int length; /// /// Gets the length of the segment. /// /// /// The length. /// public int Length { get { return length; } } /// /// Gets the end offset of the segment. /// /// /// EndOffset = Offset + Length; /// /// /// The end offset. /// public int EndOffset { get { return Offset + Length; } } /// /// Gets a value indicating whether this instance is empty. /// /// /// true if this instance is empty; otherwise, false. /// public bool IsEmpty { get { return Length == 0; } } /// /// Gets a value indicating whether this instance is invalid. /// /// /// true if this instance is invalid; otherwise, false. /// public bool IsInvalid { get { return Offset < 0; } } /// /// Initializes a new instance of the struct. /// /// /// The offset of the segment. /// /// /// The length of the segment. /// protected AbstractSegment (int offset, int length) { this.offset = offset; this.length = length; } /// /// Initializes a new instance of the struct. /// protected AbstractSegment (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); this.offset = segment.Offset; this.length = segment.Length; } } /// /// Extension methods for . /// public static class ISegmentExtensions { /// /// Gets whether fully contains the specified segment. /// /// /// Use segment.Contains(offset, 0) to detect whether a segment (end inclusive) contains offset; /// use segment.Contains(offset, 1) to detect whether a segment (end exclusive) contains offset. /// public static bool Contains (this ISegment segment, int offset, int length) { if (segment == null) throw new ArgumentNullException ("segment"); return segment.Offset <= offset && offset + length <= segment.EndOffset; } /// /// Gets whether fully contains the specified segment. /// public static bool Contains (this ISegment segment, ISegment span) { if (segment == null) throw new ArgumentNullException ("segment"); if (span == null) throw new ArgumentNullException ("span"); return segment.Offset <= span.Offset && span.EndOffset <= segment.EndOffset; } /// /// Gets whether the offset is within the . /// public static bool Contains (this ISegment segment, int offset) { if (segment == null) throw new ArgumentNullException ("segment"); return unchecked((uint)(offset - segment.Offset) < (uint)segment.Length); } /// /// Determines whether 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. /// public static bool OverlapsWith (this ISegment segment, ISegment other) { int overlapStart = Math.Max (segment.Offset, other.Offset); int overlapEnd = Math.Min (segment.EndOffset, other.EndOffset); return overlapStart < overlapEnd; } public static ISegment AdjustSegment (this ISegment segment, TextChangeEventArgs args) { if (segment == null) throw new ArgumentNullException ("segment"); if (args.Offset < segment.Offset) return new TextSegment (segment.Offset + args.InsertionLength - args.RemovalLength, segment.Length); if (args.Offset <= segment.EndOffset) return new TextSegment (segment.Offset, segment.Length); return segment; } public static IEnumerable AdjustSegments (this IEnumerable segments, TextChangeEventArgs args) { if (segments == null) throw new ArgumentNullException ("segments"); foreach (var segment in segments) { yield return segment.AdjustSegment (args); } } } }