Browse Source

Add function node support to Rope<T>.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4619 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
a40cc4c22d
  1. 3
      src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin
  2. 64
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs
  3. 188
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs
  4. 246
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs
  5. 9
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs
  6. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs
  7. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
  8. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs

3
src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
<AddIn name = "Workflow Designer"
author = "Daniel Grunwald"
description = "Windows Workflow Foundation 4.0 Designer">
description = "Windows Workflow Foundation 4.0 Designer"
addInManagerHidden="preinstalled">
<Runtime>
<Import assembly = "WorkflowDesigner.dll"/>

64
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
/*
* Created by SharpDevelop.
* User: Daniel
* Date: 03.08.2009
* Time: 20:43
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Globalization;
using System.Text;
@ -39,9 +38,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -39,9 +38,9 @@ namespace ICSharpCode.AvalonEdit.Document
{
if (rope == null)
throw new ArgumentNullException("rope");
StringBuilder b = new StringBuilder(length);
rope.WriteTo(b, startIndex, length);
return b.ToString();
char[] buffer = new char[length];
rope.CopyTo(startIndex, buffer, 0, length);
return new string(buffer);
}
/// <summary>
@ -102,8 +101,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -102,8 +101,7 @@ namespace ICSharpCode.AvalonEdit.Document
if (text.Length == 0) {
return RopeNode<char>.emptyRopeNode;
}
int nodeCount = (text.Length + RopeNode<char>.NodeSize - 1) / RopeNode<char>.NodeSize;
RopeNode<char> node = RopeNode<char>.CreateNodes(nodeCount);
RopeNode<char> node = RopeNode<char>.CreateNodes(text.Length);
// TODO: store data
return node;
*/
@ -112,8 +110,13 @@ namespace ICSharpCode.AvalonEdit.Document @@ -112,8 +110,13 @@ namespace ICSharpCode.AvalonEdit.Document
internal static void WriteTo(this RopeNode<char> node, int index, StringBuilder output, int count)
{
if (node.height == 0) {
// leaf node: append data
output.Append(node.contents, index, count);
if (node.contents == null) {
// function node
node.GetContentNode().WriteTo(index, output, count);
} else {
// leaf node: append data
output.Append(node.contents, index, count);
}
} else {
// concat node: do recursive calls
if (index + count <= node.left.length) {
@ -127,36 +130,5 @@ namespace ICSharpCode.AvalonEdit.Document @@ -127,36 +130,5 @@ namespace ICSharpCode.AvalonEdit.Document
}
}
}
/*
internal static RopeNode<char> Insert(this RopeNode<char> node, int offset, string newText)
{
if (node.height == 0) {
if (node.length + newText.Length < RopeNode<char>.NodeSize) {
RopeNode<char> result = node.CloneIfShared();
int lengthAfterOffset = node.length - offset;
char[] arr = result.contents;
for (int i = lengthAfterOffset; i >= 0; i--) {
arr[i + offset + newText.Length] = arr[i + offset];
}
newText.CopyTo(0, arr, offset, newText.Length);
result.length += newText.Length;
return result;
} else {
// TODO: implement this more efficiently
return node.Insert(offset, InitFromString(newText));
}
} else {
RopeNode<char> result = node.CloneIfShared();
if (offset < result.left.length) {
result.left = result.left.Insert(offset, newText);
} else {
result.right = result.right.Insert(offset - result.left.length, newText);
}
result.length += newText.Length;
result.Rebalance();
return result;
}
}*/
}
}

188
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs

@ -75,6 +75,52 @@ namespace ICSharpCode.AvalonEdit.Document @@ -75,6 +75,52 @@ namespace ICSharpCode.AvalonEdit.Document
this.root.CheckInvariants();
}
/// <summary>
/// Creates a rope from a part of the array.
/// This operation runs in O(N).
/// </summary>
/// <exception cref="ArgumentNullException">input is null.</exception>
public Rope(T[] array, int arrayIndex, int count)
{
VerifyArrayWithRange(array, arrayIndex, count);
this.root = RopeNode<T>.CreateFromArray(array, arrayIndex, count);
this.root.CheckInvariants();
}
/// <summary>
/// Creates a new rope that lazily initalizes its content.
/// </summary>
/// <param name="length">The length of the rope that will be lazily loaded.</param>
/// <param name="initializer">
/// The callback that provides the content for this rope.
/// <paramref name="initializer"/> will be called exactly once when the content of this rope is first requested.
/// It must return a rope with the specified length.
/// Because the initializer function is not called when a rope is cloned, and such clones may be used on another threads,
/// it is possible for the initializer callback to occur on any thread.
/// </param>
/// <remarks>
/// Any modifications inside the rope will also cause the content to be initialized.
/// However, insertions at the beginning and the end, as well as inserting this rope into another or
/// using the <see cref="Concat(Rope{T},Rope{T})"/> method, allows constructions of larger ropes where parts are
/// lazyly loaded.
/// However, even methods like Concat may sometimes cause the initializer function to be called, e.g. when
/// two short ropes are concatenated.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
public Rope(int length, Func<Rope<T>> initializer)
{
if (initializer == null)
throw new ArgumentNullException("initializer");
if (length < 0)
throw new ArgumentOutOfRangeException("length", length, "Length must not be negative");
if (length == 0) {
this.root = RopeNode<T>.emptyRopeNode;
} else {
this.root = new FunctionNode<T>(length, initializer);
}
this.root.CheckInvariants();
}
static T[] ToArray(IEnumerable<T> input)
{
T[] arr = input as T[];
@ -148,8 +194,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -148,8 +194,6 @@ namespace ICSharpCode.AvalonEdit.Document
}
if (newElements == null)
throw new ArgumentNullException("newElements");
if (newElements.Length == 0)
return;
newElements.root.Publish();
root = root.Insert(index, newElements.root);
OnChanged();
@ -186,17 +230,17 @@ namespace ICSharpCode.AvalonEdit.Document @@ -186,17 +230,17 @@ namespace ICSharpCode.AvalonEdit.Document
throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture));
}
VerifyArrayWithRange(array, arrayIndex, count);
if (count == 0)
return;
root = root.Insert(index, array, arrayIndex, count);
OnChanged();
if (count > 0) {
root = root.Insert(index, array, arrayIndex, count);
OnChanged();
}
}
/// <summary>
/// Inserts a piece of text in this rope.
/// Runs in O(lg N + M), where M is the length of the new text.
/// </summary>
/// <exception cref="ArgumentNullException">newText is null.</exception>
/// <exception cref="ArgumentNullException">newElements is null.</exception>
public void AddRange(IEnumerable<T> newElements)
{
InsertRange(this.Length, newElements);
@ -212,6 +256,16 @@ namespace ICSharpCode.AvalonEdit.Document @@ -212,6 +256,16 @@ namespace ICSharpCode.AvalonEdit.Document
InsertRange(this.Length, newElements);
}
/// <summary>
/// Appends new elements to the end of this rope.
/// Runs in O(lg N + M).
/// </summary>
/// <exception cref="ArgumentNullException">array is null.</exception>
public void AddRange(T[] array, int arrayIndex, int count)
{
InsertRange(this.Length, array, arrayIndex, count);
}
/// <summary>
/// Removes a piece of text from the rope.
/// Runs in O(lg N).
@ -220,14 +274,24 @@ namespace ICSharpCode.AvalonEdit.Document @@ -220,14 +274,24 @@ namespace ICSharpCode.AvalonEdit.Document
public void RemoveRange(int index, int count)
{
VerifyRange(index, count);
if (count == 0)
return;
if (index == 0 && count == this.Length) {
Clear();
return;
if (count > 0) {
root = root.RemoveRange(index, count);
OnChanged();
}
}
/// <summary>
/// Copies a range of the specified array into the rope, overwriting existing elements.
/// Runs in O(lg N + M).
/// </summary>
public void SetRange(int index, T[] array, int arrayIndex, int count)
{
VerifyRange(index, count);
VerifyArrayWithRange(array, arrayIndex, count);
if (count > 0) {
root = root.StoreElements(index, array, arrayIndex, count);
OnChanged();
}
root = root.RemoveRange(index, count);
OnChanged();
}
/// <summary>
@ -312,8 +376,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -312,8 +376,6 @@ namespace ICSharpCode.AvalonEdit.Document
#endregion
*/
/*
#region Operator +
/// <summary>
/// Concatenates two ropes. The input ropes are not modified.
/// Runs in O(lg N + lg M).
@ -321,42 +383,34 @@ namespace ICSharpCode.AvalonEdit.Document @@ -321,42 +383,34 @@ namespace ICSharpCode.AvalonEdit.Document
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public static Rope operator +(Rope left, Rope right)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
public static Rope<T> Concat(Rope<T> left, Rope<T> right)
{
if (left == null)
throw new ArgumentNullException("left");
if (right == null)
throw new ArgumentNullException("right");
left.root.Publish();
right.root.Publish();
return new Rope(RopeNode.Concat(left.root, right.root));
}
/// <summary>
/// Concatenates a rope and a string. The input rope is not modified.
/// Runs in O(lg N + M).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public static Rope operator +(Rope left, string right)
{
Rope result = left.Clone();
result.AppendInPlace(right);
return result;
return new Rope<T>(RopeNode<T>.Concat(left.root, right.root));
}
/// <summary>
/// Concatenates a string and a rope. The input rope is not modified.
/// Runs in O(N + lg M).
/// Concatenates multiple ropes. The input ropes are not modified.
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public static Rope operator +(string left, Rope right)
{
Rope result = right.Clone();
result.InsertInPlace(0, left);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
public static Rope<T> Concat(params Rope<T>[] ropes)
{
if (ropes == null)
throw new ArgumentNullException("ropes");
Rope<T> result = new Rope<T>();
foreach (Rope<T> r in ropes)
result.AddRange(r);
return result;
}
#endregion
*/
#region Caches / Changed event
struct RopeCacheEntry
@ -377,6 +431,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -377,6 +431,7 @@ namespace ICSharpCode.AvalonEdit.Document
}
// cached pointer to 'last used node', used to speed up accesses by index that are close together
[NonSerialized]
volatile ImmutableStack<RopeCacheEntry> lastUsedNodeStack;
internal void OnChanged()
@ -413,6 +468,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -413,6 +468,7 @@ namespace ICSharpCode.AvalonEdit.Document
/* Here's a try at implementing the setter using the cached node stack (UNTESTED code!).
* However I don't use the code because it's complicated and doesn't integrate correctly with change notifications.
* Instead, I'll use the much easier to understand recursive solution.
* Oh, and it also doesn't work correctly with function nodes.
ImmutableStack<RopeCacheEntry> nodeStack = FindNodeUsingCache(offset);
RopeCacheEntry entry = nodeStack.Peek();
if (!entry.node.isShared) {
@ -464,9 +520,18 @@ namespace ICSharpCode.AvalonEdit.Document @@ -464,9 +520,18 @@ namespace ICSharpCode.AvalonEdit.Document
stack = stack.Pop();
while (true) {
RopeCacheEntry entry = stack.Peek();
// check if we've reached the leaf node
if (entry.node.height == 0)
break;
// check if we've reached a leaf or function node
if (entry.node.height == 0) {
if (entry.node.contents == null) {
// this is a function node - go down into its subtree
entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex);
// entry is now guaranteed NOT to be another function node
}
if (entry.node.contents != null) {
// this is a node containing actual content, so we're done
break;
}
}
// go down towards leaves
if (index - entry.nodeStartIndex >= entry.node.left.length)
stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length));
@ -482,7 +547,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -482,7 +547,7 @@ namespace ICSharpCode.AvalonEdit.Document
}
// this method guarantees that it finds a leaf node
Debug.Assert(stack.Peek().node.height == 0);
Debug.Assert(stack.Peek().node.contents != null);
return stack;
}
#endregion
@ -557,6 +622,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -557,6 +622,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Runs in O(N).
/// </summary>
/// <returns>The index of the first occurance of item, or -1 if it cannot be found.</returns>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public int IndexOf(T item)
{
int index = 0;
@ -599,6 +667,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -599,6 +667,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Searches the item in the rope.
/// Runs in O(N).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public bool Contains(T item)
{
return IndexOf(item) >= 0;
@ -608,6 +679,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -608,6 +679,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Copies the whole content of the rope into the specified array.
/// Runs in O(N).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public void CopyTo(T[] array, int arrayIndex)
{
CopyTo(0, array, arrayIndex, this.Length);
@ -617,6 +691,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -617,6 +691,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Copies the a part of the rope into the specified array.
/// Runs in O(lg N + M).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public void CopyTo(int index, T[] array, int arrayIndex, int count)
{
VerifyRange(index, count);
@ -641,8 +718,11 @@ namespace ICSharpCode.AvalonEdit.Document @@ -641,8 +718,11 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary>
/// Retrieves an enumerator to iterate through the rope.
/// The enumerator will reflect the state of the rope from the GetEnumerator() call, further modifications
/// to the rope will not be reflected in the enumerator.
/// to the rope will not be visible to the enumerator.
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public IEnumerator<T> GetEnumerator()
{
this.root.Publish();
@ -653,6 +733,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -653,6 +733,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Creates an array and copies the contents of the rope into it.
/// Runs in O(N).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public T[] ToArray()
{
T[] arr = new T[this.Length];
@ -664,6 +747,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -664,6 +747,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// Creates an array and copies the contents of the rope into it.
/// Runs in O(N).
/// </summary>
/// <remarks>
/// This method counts as a read access and may be called concurrently to other read accesses.
/// </remarks>
public T[] ToArray(int startIndex, int count)
{
VerifyRange(startIndex, count);
@ -675,9 +761,15 @@ namespace ICSharpCode.AvalonEdit.Document @@ -675,9 +761,15 @@ namespace ICSharpCode.AvalonEdit.Document
static IEnumerator<T> Enumerate(RopeNode<T> node)
{
Stack<RopeNode<T>> stack = new Stack<RopeNode<T>>();
while (true) {
while (node != null) {
// go to leftmost node, pushing the right parts that we'll have to visit later
while (node.left != null) {
while (node.contents == null) {
if (node.height == 0) {
// go down into function nodes
node = node.GetContentNode();
continue;
}
Debug.Assert(node.right != null);
stack.Push(node.right);
node = node.left;
}
@ -689,7 +781,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -689,7 +781,7 @@ namespace ICSharpCode.AvalonEdit.Document
if (stack.Count > 0)
node = stack.Pop();
else
break;
node = null;
}
}

246
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs

@ -14,8 +14,14 @@ using System.Text; @@ -14,8 +14,14 @@ using System.Text;
namespace ICSharpCode.AvalonEdit.Document
{
// Class used to represent a node in the tree.
// There are three types of nodes:
// Concat nodes: height>0, left!=null, right!=null, contents==null
// Leaf nodes: height==0, left==null, right==null, contents!=null
// Function nodes: height==0, left==null, right==null, contents==null, are of type FunctionNode<T>
[Serializable]
sealed class RopeNode<T>
class RopeNode<T>
{
internal const int NodeSize = 256;
@ -29,7 +35,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -29,7 +35,7 @@ namespace ICSharpCode.AvalonEdit.Document
// the height of this subtree: 0 for leaf nodes; 1+max(left.height,right.height) for concat nodes
internal byte height;
// The character data. Only non-null for leaf nodes (height=0).
// The character data. Only non-null for leaf nodes (height=0) that aren't function nodes.
internal T[] contents;
internal int Balance {
@ -41,8 +47,14 @@ namespace ICSharpCode.AvalonEdit.Document @@ -41,8 +47,14 @@ namespace ICSharpCode.AvalonEdit.Document
{
if (height == 0) {
Debug.Assert(left == null && right == null);
Debug.Assert(contents != null && contents.Length == NodeSize);
Debug.Assert(length >= 0 && length <= NodeSize);
if (contents == null) {
Debug.Assert(this is FunctionNode<T>);
Debug.Assert(length > 0);
Debug.Assert(isShared);
} else {
Debug.Assert(contents != null && contents.Length == NodeSize);
Debug.Assert(length >= 0 && length <= NodeSize);
}
} else {
Debug.Assert(left != null && right != null);
Debug.Assert(contents == null);
@ -64,6 +76,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -64,6 +76,9 @@ namespace ICSharpCode.AvalonEdit.Document
internal RopeNode<T> Clone()
{
if (height == 0) {
// If a function node needs cloning, we'll evaluate it.
if (contents == null)
return GetContentNode().Clone();
T[] newContents = new T[NodeSize];
contents.CopyTo(newContents, 0);
return new RopeNode<T> {
@ -107,26 +122,33 @@ namespace ICSharpCode.AvalonEdit.Document @@ -107,26 +122,33 @@ namespace ICSharpCode.AvalonEdit.Document
if (length == 0) {
return emptyRopeNode;
}
int nodeCount = (length + NodeSize - 1) / NodeSize;
RopeNode<T> node = CreateNodes(nodeCount);
node.StoreElements(arr, index, length);
return node;
RopeNode<T> node = CreateNodes(length);
return node.StoreElements(0, arr, index, length);
}
internal static RopeNode<T> CreateNodes(int leafCount)
static RopeNode<T> CreateNodes(int totalLength)
{
int leafCount = (totalLength + NodeSize - 1) / NodeSize;
return CreateNodes(leafCount, totalLength);
}
static RopeNode<T> CreateNodes(int leafCount, int totalLength)
{
Debug.Assert(leafCount > 0);
Debug.Assert(totalLength > 0);
RopeNode<T> result = new RopeNode<T>();
result.length = totalLength;
if (leafCount == 1) {
return new RopeNode<T> { contents = new T[NodeSize] };
result.contents = new T[NodeSize];
} else {
int rightSide = leafCount / 2;
int leftSide = leafCount - rightSide;
RopeNode<T> result = new RopeNode<T>();
result.left = CreateNodes(leftSide);
result.right = CreateNodes(rightSide);
int leftLength = leftSide * NodeSize;
result.left = CreateNodes(leftSide, leftLength);
result.right = CreateNodes(rightSide, totalLength - leftLength);
result.height = (byte)(1 + Math.Max(result.left.height, result.right.height));
return result;
}
return result;
}
/// <summary>
@ -230,43 +252,71 @@ namespace ICSharpCode.AvalonEdit.Document @@ -230,43 +252,71 @@ namespace ICSharpCode.AvalonEdit.Document
Debug.Assert(!isShared);
if (this.length <= NodeSize) {
// convert this concat node to leaf node
// Convert this concat node to leaf node.
// We know left and right cannot be concat nodes (they would have merged already),
// but they could be function nodes.
this.height = 0;
int lengthOnLeftSide = this.left.length;
if (this.left.isShared) {
this.contents = new T[NodeSize];
Array.Copy(left.contents, 0, this.contents, 0, lengthOnLeftSide);
left.CopyTo(0, this.contents, 0, lengthOnLeftSide);
} else {
// must be a leaf node: function nodes are always marked shared
Debug.Assert(this.left.contents != null);
// steal buffer from left side
this.contents = this.left.contents;
#if DEBUG
// In debug builds, explicitly mark left node as 'damaged' - but no one else should be using it
// because it's not shared.
this.left.contents = Empty<T>.Array;
#endif
}
this.left = null;
Array.Copy(right.contents, 0, this.contents, lengthOnLeftSide, this.right.length);
right.CopyTo(0, this.contents, lengthOnLeftSide, this.right.length);
this.right = null;
}
}
/// <summary>
/// Stores the specified text in this node.
/// Copies from the array to this node.
/// </summary>
internal void StoreElements(T[] array, int arrayIndex, int count)
internal RopeNode<T> StoreElements(int index, T[] array, int arrayIndex, int count)
{
Debug.Assert(!isShared);
if (height == 0) {
length = Math.Min(NodeSize, count);
Array.Copy(array, arrayIndex, contents, 0, length);
RopeNode<T> result = this.CloneIfShared();
// result cannot be function node after a call to Clone()
if (result.height == 0) {
// leaf node:
Array.Copy(array, arrayIndex, result.contents, index, count);
} else {
left.StoreElements(array, arrayIndex, count);
right.StoreElements(array, arrayIndex + left.length, count - left.length);
length = left.length + right.length;
// concat node:
if (index + count <= result.left.length) {
result.left = result.left.StoreElements(index, array, arrayIndex, count);
} else if (index >= this.left.length) {
result.right = result.right.StoreElements(index - result.left.length, array, arrayIndex, count);
} else {
int amountInLeft = result.left.length - index;
result.left = result.left.StoreElements(index, array, arrayIndex, amountInLeft);
result.right = result.right.StoreElements(0, array, arrayIndex + amountInLeft, count - amountInLeft);
}
}
return result;
}
/// <summary>
/// Copies from this node to the array.
/// </summary>
internal void CopyTo(int index, T[] array, int arrayIndex, int count)
{
if (height == 0) {
Array.Copy(this.contents, index, array, arrayIndex, count);
if (this.contents == null) {
// function node
this.GetContentNode().CopyTo(index, array, arrayIndex, count);
} else {
// leaf node
Array.Copy(this.contents, index, array, arrayIndex, count);
}
} else {
// concat node
if (index + count <= this.left.length) {
this.left.CopyTo(index, array, arrayIndex, count);
} else if (index >= this.left.length) {
@ -282,6 +332,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -282,6 +332,7 @@ namespace ICSharpCode.AvalonEdit.Document
internal RopeNode<T> SetElement(int offset, T value)
{
RopeNode<T> result = CloneIfShared();
// result of CloneIfShared() is leaf or concat node
if (result.height == 0) {
result.contents[offset] = value;
} else if (offset < result.left.length) {
@ -301,7 +352,10 @@ namespace ICSharpCode.AvalonEdit.Document @@ -301,7 +352,10 @@ namespace ICSharpCode.AvalonEdit.Document
if (left.length + right.length <= NodeSize) {
left = left.CloneIfShared();
Array.Copy(right.contents, 0, left.contents, left.length, right.length);
// left is guaranteed to be leaf node after cloning:
// - it cannot be function node (due to clone)
// - it cannot be concat node (too short)
right.CopyTo(0, left.contents, left.length, right.length);
left.length += right.length;
return left;
} else {
@ -319,7 +373,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -319,7 +373,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// </summary>
RopeNode<T> SplitAfter(int offset)
{
Debug.Assert(!isShared && height == 0);
Debug.Assert(!isShared && height == 0 && contents != null);
RopeNode<T> newPart = new RopeNode<T>();
newPart.contents = new T[NodeSize];
newPart.length = this.length - offset;
@ -336,13 +390,15 @@ namespace ICSharpCode.AvalonEdit.Document @@ -336,13 +390,15 @@ namespace ICSharpCode.AvalonEdit.Document
return Concat(this, newElements);
}
if (height == 0) {
// we'll need to split this node
RopeNode<T> left = CloneIfShared();
// first clone this node (converts function nodes to leaf or concat nodes)
RopeNode<T> result = CloneIfShared();
if (result.height == 0) {
// leaf node: we'll need to split this node
RopeNode<T> left = result;
RopeNode<T> right = left.SplitAfter(offset);
return Concat(Concat(left, newElements), right);
} else {
RopeNode<T> result = CloneIfShared();
// concat node
if (offset < result.left.length) {
result.left = result.left.Insert(offset, newElements);
} else {
@ -358,22 +414,22 @@ namespace ICSharpCode.AvalonEdit.Document @@ -358,22 +414,22 @@ namespace ICSharpCode.AvalonEdit.Document
{
Debug.Assert(count > 0);
if (height == 0) {
if (this.length + count < RopeNode<char>.NodeSize) {
RopeNode<T> result = CloneIfShared();
int lengthAfterOffset = this.length - offset;
T[] resultContents = result.contents;
for (int i = lengthAfterOffset; i >= 0; i--) {
resultContents[i + offset + count] = resultContents[i + offset];
}
Array.Copy(array, arrayIndex, resultContents, offset, count);
result.length += count;
return result;
} else {
// TODO: implement this more efficiently?
return Insert(offset, CreateFromArray(array, arrayIndex, count));
if (this.length + count < RopeNode<char>.NodeSize) {
RopeNode<T> result = CloneIfShared();
// result must be leaf node (Clone never returns function nodes, too short for concat node)
int lengthAfterOffset = result.length - offset;
T[] resultContents = result.contents;
for (int i = lengthAfterOffset; i >= 0; i--) {
resultContents[i + offset + count] = resultContents[i + offset];
}
Array.Copy(array, arrayIndex, resultContents, offset, count);
result.length += count;
return result;
} else if (height == 0) {
// TODO: implement this more efficiently?
return Insert(offset, CreateFromArray(array, arrayIndex, count));
} else {
// this is a concat node (both leafs and function nodes are handled by the case above)
RopeNode<T> result = CloneIfShared();
if (offset < result.left.length) {
result.left = result.left.Insert(offset, array, arrayIndex, count);
@ -395,25 +451,23 @@ namespace ICSharpCode.AvalonEdit.Document @@ -395,25 +451,23 @@ namespace ICSharpCode.AvalonEdit.Document
return emptyRopeNode;
int endIndex = index + count;
if (height == 0) {
RopeNode<T> result = CloneIfShared();
RopeNode<T> result = CloneIfShared(); // convert function node to concat/leaf
if (result.height == 0) {
int remainingAfterEnd = result.length - endIndex;
for (int i = 0; i < remainingAfterEnd; i++) {
result.contents[index + i] = result.contents[endIndex + i];
}
result.length -= count;
return result;
} else {
RopeNode<T> result = CloneIfShared();
if (endIndex <= this.left.length) {
if (endIndex <= result.left.length) {
// deletion is only within the left part
result.left = result.left.RemoveRange(index, count);
} else if (index >= this.left.length) {
} else if (index >= result.left.length) {
// deletion is only within the right part
result.right = result.right.RemoveRange(index - this.left.length, count);
result.right = result.right.RemoveRange(index - result.left.length, count);
} else {
// deletion overlaps both parts
int deletionAmountOnLeftSide = this.left.length - index;
int deletionAmountOnLeftSide = result.left.length - index;
result.left = result.left.RemoveRange(index, deletionAmountOnLeftSide);
result.right = result.right.RemoveRange(0, count - deletionAmountOnLeftSide);
}
@ -426,13 +480,13 @@ namespace ICSharpCode.AvalonEdit.Document @@ -426,13 +480,13 @@ namespace ICSharpCode.AvalonEdit.Document
result.length -= count;
result.MergeIfPossible();
result.Rebalance();
return result;
}
return result;
}
#region Debug Output
#if DEBUG
void AppendTreeToString(StringBuilder b, int indent)
internal virtual void AppendTreeToString(StringBuilder b, int indent)
{
b.AppendLine(ToString());
indent += 2;
@ -469,5 +523,83 @@ namespace ICSharpCode.AvalonEdit.Document @@ -469,5 +523,83 @@ namespace ICSharpCode.AvalonEdit.Document
}
#endif
#endregion
/// <summary>
/// Gets the root node of the subtree from a lazily evaluated function node.
/// Such nodes are always marked as shared.
/// GetContentNode() will return either a Concat or Leaf node, never another FunctionNode.
/// </summary>
internal virtual RopeNode<T> GetContentNode()
{
throw new InvalidOperationException("Called GetContentNode() on non-FunctionNode.");
}
}
sealed class FunctionNode<T> : RopeNode<T>
{
Func<Rope<T>> initializer;
RopeNode<T> cachedResults;
public FunctionNode(int length, Func<Rope<T>> initializer)
{
Debug.Assert(length > 0);
Debug.Assert(initializer != null);
this.length = length;
this.initializer = initializer;
// Function nodes are immediately shared, but cannot be cloned.
// This ensures we evaluate every initializer only once.
this.isShared = true;
}
internal override RopeNode<T> GetContentNode()
{
lock (this) {
if (this.cachedResults == null) {
if (this.initializer == null)
throw new InvalidOperationException("Trying to load this node recursively; or: a previous call to a rope initializer failed.");
Func<Rope<T>> initializerCopy = this.initializer;
this.initializer = null;
Rope<T> resultRope = initializerCopy();
if (resultRope == null)
throw new InvalidOperationException("Rope initializer returned null.");
RopeNode<T> resultNode = resultRope.root;
resultNode.Publish(); // result is shared between returned rope and the rope containing this function node
if (resultNode.length != this.length)
throw new InvalidOperationException("Rope initializer returned rope with incorrect length.");
if (resultNode.height == 0 && resultNode.contents == null) {
// ResultNode is another function node.
// We want to guarantee that GetContentNode() never returns function nodes, so we have to
// go down further in the tree.
this.cachedResults = resultNode.GetContentNode();
} else {
this.cachedResults = resultNode;
}
}
return this.cachedResults;
}
}
#if DEBUG
internal override void AppendTreeToString(StringBuilder b, int indent)
{
RopeNode<T> resultNode;
lock (this) {
b.AppendLine(ToString());
resultNode = cachedResults;
}
indent += 2;
if (resultNode != null) {
b.Append(' ', indent);
b.Append("C: ");
resultNode.AppendTreeToString(b, indent);
}
}
public override string ToString()
{
return "[FunctionNode length=" + length + " initializerRan=" + (initializer == null) + "]";
}
#endif
}
}

9
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs

@ -50,10 +50,17 @@ namespace ICSharpCode.AvalonEdit.Document @@ -50,10 +50,17 @@ namespace ICSharpCode.AvalonEdit.Document
void GoToLeftMostLeaf()
{
while (currentNode.left != null) {
while (currentNode.contents == null) {
if (currentNode.height == 0) {
// this is a function node - move to its contained rope
currentNode = currentNode.GetContentNode();
continue;
}
Debug.Assert(currentNode.right != null);
stack.Push(currentNode.right);
currentNode = currentNode.left;
}
Debug.Assert(currentNode.height == 0);
}
/// <inheritdoc/>

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs

@ -67,6 +67,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -67,6 +67,9 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary>
/// Gets/Sets the start offset of the segment.
/// </summary>
/// <remarks>
/// When setting the start offset, the end offset will change, too: the Length of the segment will stay constant.
/// </remarks>
public int StartOffset {
get {
// If the segment is not connected to a tree, we store the offset in "nodeLength".

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs

@ -122,7 +122,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -122,7 +122,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
if (b != null)
element.TextRunProperties.SetForegroundBrush(b);
}
if (color.FontWeight != null) {
if (color.FontStyle != null || color.FontWeight != null) {
Typeface tf = element.TextRunProperties.Typeface;
element.TextRunProperties.SetTypeface(new Typeface(
tf.FontFamily,

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs

@ -18,6 +18,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -18,6 +18,7 @@ namespace ICSharpCode.AvalonEdit.Utils
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
[Serializable]
public sealed class ImmutableStack<T> : IEnumerable<T>
{
/// <summary>

Loading…
Cancel
Save