RichTextKit/Topten.RichTextKit/TextRange.cs

273 строки
9.2 KiB
C#

// 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
{
/// <summary>
/// Represents a range of code points in a text document
/// </summary>
public struct TextRange
{
/// <summary>
/// Initializes a TextRange
/// </summary>
/// <param name="start">The code point index of the start of the range</param>
/// <param name="end">The code point index of the end of the range</param>
/// <param name="altPosition">Whether the caret at the end of the range should be displayed in its alternative position</param>
public TextRange(int start, int end, bool altPosition = false)
{
Start = start;
End = end;
AltPosition = altPosition;
}
/// <summary>
/// Initializes a TextRange with a non-range position
/// </summary>
/// <param name="position">The code point index of the position</param>
/// <param name="altPosition">Whether the caret should be displayed in its alternative position</param>
public TextRange(int position, bool altPosition = false)
{
Start = position;
End = position;
AltPosition = altPosition;
}
/// <summary>
/// Initializes a TextRange from a caret position
/// </summary>
/// <param name="position">The code point index of the position</param>
public TextRange(CaretPosition position)
{
Start = position.CodePointIndex;
End = position.CodePointIndex;
AltPosition = position.AltPosition;
}
/// <summary>
/// The code point index of the start of the range
/// </summary>
public int Start;
/// <summary>
/// The code point index of the end of the range
/// </summary>
public int End;
/// <summary>
/// True if the end of the range should be displayed
/// with the caret in the alt position
/// </summary>
public bool AltPosition;
/// <summary>
/// Get the length of this range
/// </summary>
/// <remarks>
/// Will return negative if the range isn't normalized
/// </remarks>
public int Length => End - Start;
/// <summary>
/// Offset this text range by the specified amount
/// </summary>
/// <param name="delta">The number of code points to offset the range by</param>
/// <returns>A new TextRange</returns>
public TextRange Offset(int delta)
{
return new TextRange(Start + delta, End + delta, AltPosition);
}
/// <summary>
/// Returns the reversed text range
/// </summary>
public TextRange Reversed
{
get
{
return new TextRange(End, Start, false);
}
}
/// <summary>
/// Returns the normalized version of the text range
/// </summary>
public TextRange Normalized
{
get
{
if (Start > End)
return Reversed;
else
return this;
}
}
/// <summary>
/// Compare this text range to another for equality
/// </summary>
/// <param name="other">The text range to compare to</param>
/// <returns>True if the text ranges are equal</returns>
public bool IsEqual(TextRange other)
{
return other.Start == Start &&
other.End == End &&
other.AltPosition == AltPosition;
}
/// <summary>
/// Check if this is actually a range
/// </summary>
public bool IsRange => Start != End;
/// <summary>
/// Get the end of the range closer to the start of the document
/// </summary>
public int Minimum => Math.Min(Start, End);
/// <summary>
/// Get the end of the range closer to the end of the document
/// </summary>
public int Maximum => Math.Max(Start, End);
/// <summary>
/// Gets the end of the range as a caret position
/// </summary>
public CaretPosition CaretPosition
{
get
{
return new CaretPosition(End, AltPosition);
}
}
/// <summary>
/// Clamp the text range to a document length
/// </summary>
/// <param name="maxCodePointIndex">The max code point index</param>
/// <returns>A clamped TextRange</returns>
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);
}
/// <summary>
/// Create an updated text range that tries to represent the same
/// piece of text from before an edit to after the edit.
/// </summary>
/// <param name="codePointIndex">The position of the edit</param>
/// <param name="oldLength">The length of text deleted</param>
/// <param name="newLength">The length of text inserted</param>
/// <returns>An updated text range</returns>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// The returned range is configured such that the 'b' range
/// is used for the end position (ie: the caret)
/// </remarks>
/// <param name="a">The first text range</param>
/// <param name="b">The second text range</param>
/// <returns>A range that encompasses both ranges</returns>
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);
}
}
/// <inheritdoc />
public override string ToString()
{
return $"{Start} → {End} (len: {Length})";
}
}
}