diff --git a/src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin b/src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin index 04c9cbc120..475465e0d1 100644 --- a/src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin +++ b/src/AddIns/DisplayBindings/WorkflowDesigner/Project/WorkflowDesigner.addin @@ -1,6 +1,7 @@ + description = "Windows Workflow Foundation 4.0 Designer" + addInManagerHidden="preinstalled"> diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs index 72ea08c68f..407f0d396f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/CharRope.cs @@ -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. - */ +// +// +// +// +// $Revision$ +// + using System; using System.Globalization; using System.Text; @@ -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); } /// @@ -102,8 +101,7 @@ namespace ICSharpCode.AvalonEdit.Document if (text.Length == 0) { return RopeNode.emptyRopeNode; } - int nodeCount = (text.Length + RopeNode.NodeSize - 1) / RopeNode.NodeSize; - RopeNode node = RopeNode.CreateNodes(nodeCount); + RopeNode node = RopeNode.CreateNodes(text.Length); // TODO: store data return node; */ @@ -112,8 +110,13 @@ namespace ICSharpCode.AvalonEdit.Document internal static void WriteTo(this RopeNode 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 } } } - - /* - internal static RopeNode Insert(this RopeNode node, int offset, string newText) - { - if (node.height == 0) { - if (node.length + newText.Length < RopeNode.NodeSize) { - RopeNode 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 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; - } - }*/ } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs index d9c83c0df6..cffcf0a380 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/Rope.cs @@ -75,6 +75,52 @@ namespace ICSharpCode.AvalonEdit.Document this.root.CheckInvariants(); } + /// + /// Creates a rope from a part of the array. + /// This operation runs in O(N). + /// + /// input is null. + public Rope(T[] array, int arrayIndex, int count) + { + VerifyArrayWithRange(array, arrayIndex, count); + this.root = RopeNode.CreateFromArray(array, arrayIndex, count); + this.root.CheckInvariants(); + } + + /// + /// Creates a new rope that lazily initalizes its content. + /// + /// The length of the rope that will be lazily loaded. + /// + /// The callback that provides the content for this rope. + /// 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. + /// + /// + /// 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 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. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Rope(int length, Func> 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.emptyRopeNode; + } else { + this.root = new FunctionNode(length, initializer); + } + this.root.CheckInvariants(); + } + static T[] ToArray(IEnumerable input) { T[] arr = input as T[]; @@ -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 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(); + } } /// /// Inserts a piece of text in this rope. /// Runs in O(lg N + M), where M is the length of the new text. /// - /// newText is null. + /// newElements is null. public void AddRange(IEnumerable newElements) { InsertRange(this.Length, newElements); @@ -212,6 +256,16 @@ namespace ICSharpCode.AvalonEdit.Document InsertRange(this.Length, newElements); } + /// + /// Appends new elements to the end of this rope. + /// Runs in O(lg N + M). + /// + /// array is null. + public void AddRange(T[] array, int arrayIndex, int count) + { + InsertRange(this.Length, array, arrayIndex, count); + } + /// /// Removes a piece of text from the rope. /// Runs in O(lg N). @@ -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(); + } + } + + /// + /// Copies a range of the specified array into the rope, overwriting existing elements. + /// Runs in O(lg N + M). + /// + 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(); } /// @@ -312,8 +376,6 @@ namespace ICSharpCode.AvalonEdit.Document #endregion */ - /* - #region Operator + /// /// Concatenates two ropes. The input ropes are not modified. /// Runs in O(lg N + lg M). @@ -321,42 +383,34 @@ namespace ICSharpCode.AvalonEdit.Document /// /// This method counts as a read access and may be called concurrently to other read accesses. /// - public static Rope operator +(Rope left, Rope right) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static Rope Concat(Rope left, Rope 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)); - } - - /// - /// Concatenates a rope and a string. The input rope is not modified. - /// Runs in O(lg N + M). - /// - /// - /// This method counts as a read access and may be called concurrently to other read accesses. - /// - public static Rope operator +(Rope left, string right) - { - Rope result = left.Clone(); - result.AppendInPlace(right); - return result; + return new Rope(RopeNode.Concat(left.root, right.root)); } /// - /// 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. /// /// /// This method counts as a read access and may be called concurrently to other read accesses. /// - 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 Concat(params Rope[] ropes) + { + if (ropes == null) + throw new ArgumentNullException("ropes"); + Rope result = new Rope(); + foreach (Rope r in ropes) + result.AddRange(r); return result; } - #endregion - */ #region Caches / Changed event struct RopeCacheEntry @@ -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 lastUsedNodeStack; internal void OnChanged() @@ -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 nodeStack = FindNodeUsingCache(offset); RopeCacheEntry entry = nodeStack.Peek(); if (!entry.node.isShared) { @@ -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 } // 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 /// Runs in O(N). /// /// The index of the first occurance of item, or -1 if it cannot be found. + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public int IndexOf(T item) { int index = 0; @@ -599,6 +667,9 @@ namespace ICSharpCode.AvalonEdit.Document /// Searches the item in the rope. /// Runs in O(N). /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public bool Contains(T item) { return IndexOf(item) >= 0; @@ -608,6 +679,9 @@ namespace ICSharpCode.AvalonEdit.Document /// Copies the whole content of the rope into the specified array. /// Runs in O(N). /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public void CopyTo(T[] array, int arrayIndex) { CopyTo(0, array, arrayIndex, this.Length); @@ -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). /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public void CopyTo(int index, T[] array, int arrayIndex, int count) { VerifyRange(index, count); @@ -641,8 +718,11 @@ namespace ICSharpCode.AvalonEdit.Document /// /// 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. /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public IEnumerator GetEnumerator() { this.root.Publish(); @@ -653,6 +733,9 @@ namespace ICSharpCode.AvalonEdit.Document /// Creates an array and copies the contents of the rope into it. /// Runs in O(N). /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public T[] ToArray() { T[] arr = new T[this.Length]; @@ -664,6 +747,9 @@ namespace ICSharpCode.AvalonEdit.Document /// Creates an array and copies the contents of the rope into it. /// Runs in O(N). /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// public T[] ToArray(int startIndex, int count) { VerifyRange(startIndex, count); @@ -675,9 +761,15 @@ namespace ICSharpCode.AvalonEdit.Document static IEnumerator Enumerate(RopeNode node) { Stack> stack = new Stack>(); - 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 if (stack.Count > 0) node = stack.Pop(); else - break; + node = null; } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs index dbd958dadf..461846acf7 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeNode.cs @@ -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 + [Serializable] - sealed class RopeNode + class RopeNode { internal const int NodeSize = 256; @@ -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 { 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); + 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 internal RopeNode 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 { @@ -107,26 +122,33 @@ namespace ICSharpCode.AvalonEdit.Document if (length == 0) { return emptyRopeNode; } - int nodeCount = (length + NodeSize - 1) / NodeSize; - RopeNode node = CreateNodes(nodeCount); - node.StoreElements(arr, index, length); - return node; + RopeNode node = CreateNodes(length); + return node.StoreElements(0, arr, index, length); + } + + static RopeNode CreateNodes(int totalLength) + { + int leafCount = (totalLength + NodeSize - 1) / NodeSize; + return CreateNodes(leafCount, totalLength); } - internal static RopeNode CreateNodes(int leafCount) + static RopeNode CreateNodes(int leafCount, int totalLength) { Debug.Assert(leafCount > 0); + Debug.Assert(totalLength > 0); + RopeNode result = new RopeNode(); + result.length = totalLength; if (leafCount == 1) { - return new RopeNode { contents = new T[NodeSize] }; + result.contents = new T[NodeSize]; } else { int rightSide = leafCount / 2; int leftSide = leafCount - rightSide; - RopeNode result = new RopeNode(); - 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; } /// @@ -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.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; } } /// - /// Stores the specified text in this node. + /// Copies from the array to this node. /// - internal void StoreElements(T[] array, int arrayIndex, int count) + internal RopeNode 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 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; } + /// + /// Copies from this node to the array. + /// 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 internal RopeNode SetElement(int offset, T value) { RopeNode 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 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 /// RopeNode SplitAfter(int offset) { - Debug.Assert(!isShared && height == 0); + Debug.Assert(!isShared && height == 0 && contents != null); RopeNode newPart = new RopeNode(); newPart.contents = new T[NodeSize]; newPart.length = this.length - offset; @@ -336,13 +390,15 @@ namespace ICSharpCode.AvalonEdit.Document return Concat(this, newElements); } - if (height == 0) { - // we'll need to split this node - RopeNode left = CloneIfShared(); + // first clone this node (converts function nodes to leaf or concat nodes) + RopeNode result = CloneIfShared(); + if (result.height == 0) { + // leaf node: we'll need to split this node + RopeNode left = result; RopeNode right = left.SplitAfter(offset); return Concat(Concat(left, newElements), right); } else { - RopeNode 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 { Debug.Assert(count > 0); - if (height == 0) { - if (this.length + count < RopeNode.NodeSize) { - RopeNode 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.NodeSize) { + RopeNode 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 result = CloneIfShared(); if (offset < result.left.length) { result.left = result.left.Insert(offset, array, arrayIndex, count); @@ -395,25 +451,23 @@ namespace ICSharpCode.AvalonEdit.Document return emptyRopeNode; int endIndex = index + count; - if (height == 0) { - RopeNode result = CloneIfShared(); + RopeNode 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 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 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 } #endif #endregion + + /// + /// 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. + /// + internal virtual RopeNode GetContentNode() + { + throw new InvalidOperationException("Called GetContentNode() on non-FunctionNode."); + } + } + + sealed class FunctionNode : RopeNode + { + Func> initializer; + RopeNode cachedResults; + + public FunctionNode(int length, Func> 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 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> initializerCopy = this.initializer; + this.initializer = null; + Rope resultRope = initializerCopy(); + if (resultRope == null) + throw new InvalidOperationException("Rope initializer returned null."); + RopeNode 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 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 } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs index eec3d95e7a..e704d9b99e 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextReader.cs @@ -40,7 +40,7 @@ namespace ICSharpCode.AvalonEdit.Document throw new ArgumentNullException("rope"); if (publishRope) rope.root.Publish(); - // special case for the empty rope: + // special case for the empty rope: // leave currentNode initialized to null (RopeTextReader doesn't support empty nodes) if (rope.Length != 0) { currentNode = rope.root; @@ -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); } /// diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs index 1f067a159e..cabc57fd6d 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs @@ -67,6 +67,9 @@ namespace ICSharpCode.AvalonEdit.Document /// /// Gets/Sets the start offset of the segment. /// + /// + /// When setting the start offset, the end offset will change, too: the Length of the segment will stay constant. + /// public int StartOffset { get { // If the segment is not connected to a tree, we store the offset in "nodeLength". diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs index 27437400e4..d4a5d47156 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs @@ -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, diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs index 3c90663124..a089310a4f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ImmutableStack.cs @@ -18,6 +18,7 @@ namespace ICSharpCode.AvalonEdit.Utils /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + [Serializable] public sealed class ImmutableStack : IEnumerable { ///