// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using NUnit.Framework;
using System.Collections.Generic;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class TextAnchorTest
{
TextDocument document;
[SetUp]
public void SetUp()
{
document = new TextDocument();
}
[Test]
public void AnchorInEmptyDocument()
{
TextAnchor a1 = document.CreateAnchor(0);
TextAnchor a2 = document.CreateAnchor(0);
a1.MovementType = AnchorMovementType.BeforeInsertion;
a2.MovementType = AnchorMovementType.AfterInsertion;
Assert.AreEqual(0, a1.Offset);
Assert.AreEqual(0, a2.Offset);
document.Insert(0, "x");
Assert.AreEqual(0, a1.Offset);
Assert.AreEqual(1, a2.Offset);
}
[Test]
public void AnchorsSurviveDeletion()
{
document.Text = new string(' ', 10);
TextAnchor[] a1 = new TextAnchor[11];
TextAnchor[] a2 = new TextAnchor[11];
for (int i = 0; i < 11; i++) {
//Console.WriteLine("Insert first at i = " + i);
a1[i] = document.CreateAnchor(i);
a1[i].SurviveDeletion = true;
//Console.WriteLine(document.GetTextAnchorTreeAsString());
//Console.WriteLine("Insert second at i = " + i);
a2[i] = document.CreateAnchor(i);
a2[i].SurviveDeletion = false;
//Console.WriteLine(document.GetTextAnchorTreeAsString());
}
for (int i = 0; i < 11; i++) {
Assert.AreEqual(i, a1[i].Offset);
Assert.AreEqual(i, a2[i].Offset);
}
document.Remove(1, 8);
for (int i = 0; i < 11; i++) {
if (i <= 1) {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsFalse(a2[i].IsDeleted);
Assert.AreEqual(i, a1[i].Offset);
Assert.AreEqual(i, a2[i].Offset);
} else if (i <= 8) {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsTrue(a2[i].IsDeleted);
Assert.AreEqual(1, a1[i].Offset);
} else {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsFalse(a2[i].IsDeleted);
Assert.AreEqual(i - 8, a1[i].Offset);
Assert.AreEqual(i - 8, a2[i].Offset);
}
}
}
Random rnd;
[TestFixtureSetUp]
public void FixtureSetup()
{
int seed = Environment.TickCount;
Console.WriteLine("TextAnchorTest Seed: " + seed);
rnd = new Random(seed);
}
[Test]
public void CreateAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int i = 0; i < 1000; i++) {
int offset = rnd.Next(1000);
anchors.Add(document.CreateAnchor(offset));
expectedOffsets.Add(offset);
}
for (int i = 0; i < anchors.Count; i++) {
Assert.AreEqual(expectedOffsets[i], anchors[i].Offset);
}
GC.KeepAlive(anchors);
}
[Test]
public void CreateAndGCAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int t = 0; t < 250; t++) {
int c = rnd.Next(50);
if (rnd.Next(2) == 0) {
for (int i = 0; i < c; i++) {
int offset = rnd.Next(1000);
anchors.Add(document.CreateAnchor(offset));
expectedOffsets.Add(offset);
}
} else if (c <= anchors.Count) {
anchors.RemoveRange(0, c);
expectedOffsets.RemoveRange(0, c);
GC.Collect();
}
for (int j = 0; j < anchors.Count; j++) {
Assert.AreEqual(expectedOffsets[j], anchors[j].Offset);
}
}
GC.KeepAlive(anchors);
}
[Test]
public void MoveAnchorsDuringReplace()
{
document.Text = "abcd";
TextAnchor start = document.CreateAnchor(1);
TextAnchor middleDeletable = document.CreateAnchor(2);
TextAnchor middleSurvivorLeft = document.CreateAnchor(2);
middleSurvivorLeft.SurviveDeletion = true;
middleSurvivorLeft.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleSurvivorRight = document.CreateAnchor(2);
middleSurvivorRight.SurviveDeletion = true;
middleSurvivorRight.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor end = document.CreateAnchor(3);
document.Replace(1, 2, "BxC");
Assert.AreEqual(1, start.Offset);
Assert.IsTrue(middleDeletable.IsDeleted);
Assert.AreEqual(1, middleSurvivorLeft.Offset);
Assert.AreEqual(4, middleSurvivorRight.Offset);
Assert.AreEqual(4, end.Offset);
}
[Test]
public void CreateAndMoveAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int t = 0; t < 250; t++) {
//Console.Write("t = " + t + " ");
int c = rnd.Next(50);
switch (rnd.Next(5)) {
case 0:
//Console.WriteLine("Add c=" + c + " anchors");
for (int i = 0; i < c; i++) {
int offset = rnd.Next(document.TextLength);
TextAnchor anchor = document.CreateAnchor(offset);
if (rnd.Next(2) == 0)
anchor.MovementType = AnchorMovementType.BeforeInsertion;
else
anchor.MovementType = AnchorMovementType.AfterInsertion;
anchor.SurviveDeletion = rnd.Next(2) == 0;
anchors.Add(anchor);
expectedOffsets.Add(offset);
}
break;
case 1:
if (c <= anchors.Count) {
//Console.WriteLine("Remove c=" + c + " anchors");
anchors.RemoveRange(0, c);
expectedOffsets.RemoveRange(0, c);
GC.Collect();
}
break;
case 2:
int insertOffset = rnd.Next(document.TextLength);
int insertLength = rnd.Next(1000);
//Console.WriteLine("insertOffset=" + insertOffset + " insertLength="+insertLength);
document.Insert(insertOffset, new string(' ', insertLength));
for (int i = 0; i < anchors.Count; i++) {
if (anchors[i].MovementType == AnchorMovementType.BeforeInsertion) {
if (expectedOffsets[i] > insertOffset)
expectedOffsets[i] += insertLength;
} else {
if (expectedOffsets[i] >= insertOffset)
expectedOffsets[i] += insertLength;
}
}
break;
case 3:
int removalOffset = rnd.Next(document.TextLength);
int removalLength = rnd.Next(document.TextLength - removalOffset);
//Console.WriteLine("RemovalOffset=" + removalOffset + " RemovalLength="+removalLength);
document.Remove(removalOffset, removalLength);
for (int i = anchors.Count - 1; i >= 0; i--) {
if (expectedOffsets[i] > removalOffset && expectedOffsets[i] < removalOffset + removalLength) {
if (anchors[i].SurviveDeletion) {
expectedOffsets[i] = removalOffset;
} else {
Assert.IsTrue(anchors[i].IsDeleted);
anchors.RemoveAt(i);
expectedOffsets.RemoveAt(i);
}
} else if (expectedOffsets[i] > removalOffset) {
expectedOffsets[i] -= removalLength;
}
}
break;
case 4:
int replaceOffset = rnd.Next(document.TextLength);
int replaceRemovalLength = rnd.Next(document.TextLength - replaceOffset);
int replaceInsertLength = rnd.Next(1000);
//Console.WriteLine("ReplaceOffset=" + replaceOffset + " RemovalLength="+replaceRemovalLength + " InsertLength=" + replaceInsertLength);
document.Replace(replaceOffset, replaceRemovalLength, new string(' ', replaceInsertLength));
for (int i = anchors.Count - 1; i >= 0; i--) {
if (expectedOffsets[i] > replaceOffset && expectedOffsets[i] < replaceOffset + replaceRemovalLength) {
if (anchors[i].SurviveDeletion) {
if (anchors[i].MovementType == AnchorMovementType.AfterInsertion)
expectedOffsets[i] = replaceOffset + replaceInsertLength;
else
expectedOffsets[i] = replaceOffset;
} else {
Assert.IsTrue(anchors[i].IsDeleted);
anchors.RemoveAt(i);
expectedOffsets.RemoveAt(i);
}
} else if (expectedOffsets[i] > replaceOffset) {
expectedOffsets[i] += replaceInsertLength - replaceRemovalLength;
} else if (expectedOffsets[i] == replaceOffset && replaceRemovalLength == 0 && anchors[i].MovementType == AnchorMovementType.AfterInsertion) {
expectedOffsets[i] += replaceInsertLength - replaceRemovalLength;
}
}
break;
}
Assert.AreEqual(anchors.Count, expectedOffsets.Count);
for (int j = 0; j < anchors.Count; j++) {
Assert.AreEqual(expectedOffsets[j], anchors[j].Offset);
}
}
GC.KeepAlive(anchors);
}
[Test]
public void RepeatedTextDragDrop()
{
document.Text = new string(' ', 1000);
for (int i = 0; i < 20; i++) {
TextAnchor a = document.CreateAnchor(144);
TextAnchor b = document.CreateAnchor(157);
document.Insert(128, new string('a', 13));
document.Remove(157, 13);
a = document.CreateAnchor(128);
b = document.CreateAnchor(141);
document.Insert(157, new string('b', 13));
document.Remove(128, 13);
a = null;
b = null;
if ((i % 5) == 0)
GC.Collect();
}
}
[Test]
public void ReplaceSpacesWithTab()
{
document.Text = "a b";
TextAnchor before = document.CreateAnchor(1);
before.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor after = document.CreateAnchor(5);
TextAnchor survivingMiddle = document.CreateAnchor(2);
TextAnchor deletedMiddle = document.CreateAnchor(3);
document.Replace(1, 4, "\t", OffsetChangeMappingType.CharacterReplace);
Assert.AreEqual("a\tb", document.Text);
// yes, the movement is a bit strange; but that's how CharacterReplace works when the text gets shorter
Assert.AreEqual(1, before.Offset);
Assert.AreEqual(2, after.Offset);
Assert.AreEqual(2, survivingMiddle.Offset);
Assert.AreEqual(2, deletedMiddle.Offset);
}
[Test]
public void ReplaceTwoCharactersWithThree()
{
document.Text = "a12b";
TextAnchor before = document.CreateAnchor(1);
before.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor after = document.CreateAnchor(3);
before.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleB = document.CreateAnchor(2);
before.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleA = document.CreateAnchor(2);
before.MovementType = AnchorMovementType.AfterInsertion;
document.Replace(1, 2, "123", OffsetChangeMappingType.CharacterReplace);
Assert.AreEqual("a123b", document.Text);
Assert.AreEqual(1, before.Offset);
Assert.AreEqual(4, after.Offset);
Assert.AreEqual(2, middleA.Offset);
Assert.AreEqual(2, middleB.Offset);
}
}
}