// RichTextKit
// Copyright © 2019-2020 Topten Software. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
using System;
using System.IO;
using System.Xml.Schema;
namespace Topten.RichTextKit
{
///
/// Represents a range of code points in a text document
///
public struct TextRange
{
///
/// Initializes a TextRange
///
/// The code point index of the start of the range
/// The code point index of the end of the range
/// Whether the caret at the end of the range should be displayed in its alternative position
public TextRange(int start, int end, bool altPosition = false)
{
Start = start;
End = end;
AltPosition = altPosition;
}
///
/// Initializes a TextRange with a non-range position
///
/// The code point index of the position
/// Whether the caret should be displayed in its alternative position
public TextRange(int position, bool altPosition = false)
{
Start = position;
End = position;
AltPosition = altPosition;
}
///
/// Initializes a TextRange from a caret position
///
/// The code point index of the position
public TextRange(CaretPosition position)
{
Start = position.CodePointIndex;
End = position.CodePointIndex;
AltPosition = position.AltPosition;
}
///
/// The code point index of the start of the range
///
public int Start;
///
/// The code point index of the end of the range
///
public int End;
///
/// True if the end of the range should be displayed
/// with the caret in the alt position
///
public bool AltPosition;
///
/// Get the length of this range
///
///
/// Will return negative if the range isn't normalized
///
public int Length => End - Start;
///
/// Offset this text range by the specified amount
///
/// The number of code points to offset the range by
/// A new TextRange
public TextRange Offset(int delta)
{
return new TextRange(Start + delta, End + delta, AltPosition);
}
///
/// Returns the reversed text range
///
public TextRange Reversed
{
get
{
return new TextRange(End, Start, false);
}
}
///
/// Returns the normalized version of the text range
///
public TextRange Normalized
{
get
{
if (Start > End)
return Reversed;
else
return this;
}
}
///
/// Compare this text range to another for equality
///
/// The text range to compare to
/// True if the text ranges are equal
public bool IsEqual(TextRange other)
{
return other.Start == Start &&
other.End == End &&
other.AltPosition == AltPosition;
}
///
/// Check if this is actually a range
///
public bool IsRange => Start != End;
///
/// Get the end of the range closer to the start of the document
///
public int Minimum => Math.Min(Start, End);
///
/// Get the end of the range closer to the end of the document
///
public int Maximum => Math.Max(Start, End);
///
/// Gets the end of the range as a caret position
///
public CaretPosition CaretPosition
{
get
{
return new CaretPosition(End, AltPosition);
}
}
///
/// Clamp the text range to a document length
///
/// The max code point index
/// A clamped TextRange
public TextRange Clamp(int maxCodePointIndex)
{
var newStart = Start;
if (newStart < 0)
newStart = 0;
if (newStart > maxCodePointIndex)
newStart = maxCodePointIndex;
var newEnd = End;
if (newEnd < 0)
newEnd = 0;
if (newEnd > maxCodePointIndex)
newEnd = maxCodePointIndex;
return new TextRange(newStart, newEnd, AltPosition);
}
///
/// Create an updated text range that tries to represent the same
/// piece of text from before an edit to after the edit.
///
/// The position of the edit
/// The length of text deleted
/// The length of text inserted
/// An updated text range
public TextRange UpdateForEdit(int codePointIndex, int oldLength, int newLength)
{
int delta = newLength - oldLength;
// After this range?
if (codePointIndex > Maximum)
return this;
// Before this range?
if (codePointIndex + oldLength <= Minimum)
return new TextRange(Start + delta, End + delta, AltPosition);
// Entire range?
if (codePointIndex <= Minimum && codePointIndex + oldLength >= Maximum)
return new TextRange(codePointIndex, codePointIndex, AltPosition);
// Inside this range?
if (codePointIndex >= Minimum && codePointIndex + oldLength <= Maximum)
{
if (Start < End)
return new TextRange(Start, End + delta, AltPosition);
else
return new TextRange(End, Start + delta, AltPosition);
}
// Overlap start of this range?
if (codePointIndex < Minimum && codePointIndex + oldLength >= Minimum)
{
if (Start < End)
return new TextRange(codePointIndex + newLength, End + delta, AltPosition);
else
return new TextRange(Start + delta, codePointIndex + newLength, AltPosition);
}
// Overlap end of this range?
if (codePointIndex >= Minimum && codePointIndex + oldLength > Maximum)
{
if (Start < End)
return new TextRange(Start, codePointIndex, AltPosition);
else
return new TextRange(codePointIndex, End, AltPosition);
}
// Should never get here.
return new TextRange();
}
///
/// Create a new range that is the union of two other ranges. The
/// union is the smallest range that contains the other two other
/// ranges.
///
///
/// The returned range is configured such that the 'b' range
/// is used for the end position (ie: the caret)
///
/// The first text range
/// The second text range
/// A range that encompasses both ranges
public static TextRange Union(TextRange a, TextRange b)
{
if (a.Minimum <= b.Minimum)
{
return new TextRange(
Math.Min(a.Minimum, b.Minimum),
Math.Max(a.Maximum, b.Maximum), b.AltPosition);
}
else
{
return new TextRange(
Math.Max(a.Maximum, b.Maximum),
Math.Min(a.Minimum, b.Minimum), b.AltPosition);
}
}
///
public override string ToString()
{
return $"{Start} → {End} (len: {Length})";
}
}
}