// Copies a run of text into a ContentContainer. // Returns the next node to examine. private TextTreeNode CopyTextNode(TextTreeTextNode textNode, TextTreeNode haltNode, out ContentContainer container) { SplayTreeNode node; char[] text; int count; int symbolOffset; Invariant.Assert(textNode != haltNode, "Expect at least one node to copy!"); symbolOffset = textNode.GetSymbolOffset(this.TextContainer.Generation); // Get a count of all the characters we're about to copy. count = 0; node = textNode; do { count += textNode.SymbolCount; node = textNode.GetNextNode(); textNode = node as TextTreeTextNode; }while (textNode != null && textNode != haltNode); // Allocate storage. text = new char[count]; // Copy the text. TextTreeText.ReadText(this.TextContainer.RootTextBlock, symbolOffset, count, text, 0 /*startIndex*/); container = new TextContentContainer(text); return((TextTreeNode)node); }
// Token: 0x06003D19 RID: 15641 RVA: 0x0011BD4A File Offset: 0x00119F4A internal TextTreeFixupNode(TextTreeNode previousNode, ElementEdge previousEdge, TextTreeNode nextNode, ElementEdge nextEdge, TextTreeNode firstContainedNode, TextTreeNode lastContainedNode) { this._previousNode = previousNode; this._previousEdge = previousEdge; this._nextNode = nextNode; this._nextEdge = nextEdge; this._firstContainedNode = firstContainedNode; this._lastContainedNode = lastContainedNode; }
// Creates a new TextTreeFixupNode instance. // This ctor should only be called when extracting a single TextTreeTextElementNode. // previousNode/Edge should point to the node TextPositions will // move to after synchronizing against the deleted content. // first/lastContainedNode point to the first and last contained nodes // of an extracted element node. Positions may move into these nodes. internal TextTreeFixupNode(TextTreeNode previousNode, ElementEdge previousEdge, TextTreeNode nextNode, ElementEdge nextEdge, TextTreeNode firstContainedNode, TextTreeNode lastContainedNode) { _previousNode = previousNode; _previousEdge = previousEdge; _nextNode = nextNode; _nextEdge = nextEdge; _firstContainedNode = firstContainedNode; _lastContainedNode = lastContainedNode; }
// Token: 0x06003D0D RID: 15629 RVA: 0x0011B8BC File Offset: 0x00119ABC internal TextTreeDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) : base(tree, start.GetSymbolOffset()) { start.DebugAssertGeneration(); end.DebugAssertGeneration(); Invariant.Assert(start.GetScopingNode() == end.GetScopingNode(), "start/end have different scope!"); TextTreeNode adjacentNode = start.GetAdjacentNode(LogicalDirection.Forward); TextTreeNode adjacentNode2 = end.GetAdjacentNode(LogicalDirection.Forward); this._content = this.CopyContent(adjacentNode, adjacentNode2); }
// Dumps a node and all contained nodes "flat" -- in notation similar to xaml. internal static void DumpFlat(TextTreeNode node) { Debug.Write("<" + GetFlatPrefix(node) + node.DebugId); if (node.ContainedNode != null) { Debug.Write(">"); DumpNodeFlatRecursive(node.GetFirstContainedNode()); Debug.WriteLine("</" + GetFlatPrefix(node) + node.DebugId + ">"); } else { Debug.WriteLine("/>"); } }
//------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods // Walks the tree from node to the end of its sibling list, // copying content along the way, // halting when/if haltNode is encountered. haltNode may be // null, in which case we walk to the end of the sibling list. // // Returns a ContentContainer holding a deep copy of all content // walked. // // This method is called recursively when TextElement nodes // are encountered. private ContentContainer CopyContent(TextTreeNode node, TextTreeNode haltNode) { ContentContainer firstContainer; ContentContainer container; ContentContainer nextContainer; TextTreeTextNode textNode; TextTreeObjectNode objectNode; TextTreeTextElementNode elementNode; firstContainer = null; container = null; while (node != haltNode && node != null) { textNode = node as TextTreeTextNode; if (textNode != null) { node = CopyTextNode(textNode, haltNode, out nextContainer); } else { objectNode = node as TextTreeObjectNode; if (objectNode != null) { node = CopyObjectNode(objectNode, out nextContainer); } else { Invariant.Assert(node is TextTreeTextElementNode, "Unexpected TextTreeNode type!"); elementNode = (TextTreeTextElementNode)node; node = CopyElementNode(elementNode, out nextContainer); } } if (container == null) { firstContainer = nextContainer; } else { container.NextContainer = nextContainer; } container = nextContainer; } return(firstContainer); }
// Token: 0x06003D13 RID: 15635 RVA: 0x0011BAC8 File Offset: 0x00119CC8 private TextTreeNode CopyTextNode(TextTreeTextNode textNode, TextTreeNode haltNode, out TextTreeDeleteContentUndoUnit.ContentContainer container) { Invariant.Assert(textNode != haltNode, "Expect at least one node to copy!"); int symbolOffset = textNode.GetSymbolOffset(base.TextContainer.Generation); int num = 0; SplayTreeNode nextNode; do { num += textNode.SymbolCount; nextNode = textNode.GetNextNode(); textNode = (nextNode as TextTreeTextNode); }while (textNode != null && textNode != haltNode); char[] array = new char[num]; TextTreeText.ReadText(base.TextContainer.RootTextBlock, symbolOffset, num, array, 0); container = new TextTreeDeleteContentUndoUnit.TextContentContainer(array); return((TextTreeNode)nextNode); }
// Token: 0x06003D12 RID: 15634 RVA: 0x0011BA44 File Offset: 0x00119C44 private TextTreeDeleteContentUndoUnit.ContentContainer CopyContent(TextTreeNode node, TextTreeNode haltNode) { TextTreeDeleteContentUndoUnit.ContentContainer result = null; TextTreeDeleteContentUndoUnit.ContentContainer contentContainer = null; while (node != haltNode && node != null) { TextTreeTextNode textTreeTextNode = node as TextTreeTextNode; TextTreeDeleteContentUndoUnit.ContentContainer contentContainer2; if (textTreeTextNode != null) { node = this.CopyTextNode(textTreeTextNode, haltNode, out contentContainer2); } else { TextTreeObjectNode textTreeObjectNode = node as TextTreeObjectNode; if (textTreeObjectNode != null) { node = this.CopyObjectNode(textTreeObjectNode, out contentContainer2); } else { Invariant.Assert(node is TextTreeTextElementNode, "Unexpected TextTreeNode type!"); TextTreeTextElementNode elementNode = (TextTreeTextElementNode)node; node = this.CopyElementNode(elementNode, out contentContainer2); } } if (contentContainer == null) { result = contentContainer2; } else { contentContainer.NextContainer = contentContainer2; } contentContainer = contentContainer2; } return(result); }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Creates a new TextTreeFixupNode instance. // previousNode/Edge should point to the node TextPositions will // move to after synchronizing against the deleted content. internal TextTreeFixupNode(TextTreeNode previousNode, ElementEdge previousEdge, TextTreeNode nextNode, ElementEdge nextEdge) : this(previousNode, previousEdge, nextNode, nextEdge, null, null) { }
// Copies a run of text into a ContentContainer. // Returns the next node to examine. private TextTreeNode CopyTextNode(TextTreeTextNode textNode, TextTreeNode haltNode, out ContentContainer container) { SplayTreeNode node; char[] text; int count; int symbolOffset; Invariant.Assert(textNode != haltNode, "Expect at least one node to copy!"); symbolOffset = textNode.GetSymbolOffset(this.TextContainer.Generation); // Get a count of all the characters we're about to copy. count = 0; node = textNode; do { count += textNode.SymbolCount; node = textNode.GetNextNode(); textNode = node as TextTreeTextNode; } while (textNode != null && textNode != haltNode); // Allocate storage. text = new char[count]; // Copy the text. TextTreeText.ReadText(this.TextContainer.RootTextBlock, symbolOffset, count, text, 0 /*startIndex*/); container = new TextContentContainer(text); return (TextTreeNode)node; }
// Returns the symbol offset within the TextContainer of this Position. internal static int GetSymbolOffset(TextContainer tree, TextTreeNode node, ElementEdge edge) { int offset; switch (edge) { case ElementEdge.BeforeStart: offset = node.GetSymbolOffset(tree.Generation); break; case ElementEdge.AfterStart: offset = node.GetSymbolOffset(tree.Generation) + 1; break; case ElementEdge.BeforeEnd: offset = node.GetSymbolOffset(tree.Generation) + node.SymbolCount - 1; break; case ElementEdge.AfterEnd: offset = node.GetSymbolOffset(tree.Generation) + node.SymbolCount; break; default: Invariant.Assert(false, "Unknown value for position edge"); offset = 0; break; } return offset; }
// Fires Change events for IMELeftEdgeCharCount deltas to a node after it // moves out of position such that it is no longer the leftmost child // of its parent. private void RaiseEventForFormerFirstIMEVisibleNode(TextTreeNode node) { TextPointer startEdgePosition = new TextPointer(this, node, ElementEdge.BeforeStart); // Next node was the old first node. Its IMECharCount // just bumped up, report that. AddChange(startEdgePosition, /* symbolCount */ 0, /* IMECharCount */ 1, PrecursorTextChangeType.ContentAdded); }
internal static TextTreeNode GetAdjacentNode(TextTreeNode node, ElementEdge edge, LogicalDirection direction) { TextTreeNode adjacentNode; adjacentNode = GetAdjacentSiblingNode(node, edge, direction); if (adjacentNode == null) { // We're the first or last child, try the parent. if (edge == ElementEdge.AfterStart || edge == ElementEdge.BeforeEnd) { adjacentNode = node; } else { adjacentNode = (TextTreeNode)node.GetContainingNode(); } } return adjacentNode; }
// For any logical position (location between two symbols) there are two // possible node/edge pairs. This method choses the pair that fits a // specified gravity, such that future inserts won't require that a text // position be moved, based on its gravity, at the node/edge pair. private static void RepositionForGravity(ref TextTreeNode node, ref ElementEdge edge, LogicalDirection gravity) { SplayTreeNode newNode; ElementEdge newEdge; newNode = node; newEdge = edge; switch (edge) { case ElementEdge.BeforeStart: if (gravity == LogicalDirection.Backward) { newNode = node.GetPreviousNode(); newEdge = ElementEdge.AfterEnd; if (newNode == null) { newNode = node.GetContainingNode(); newEdge = ElementEdge.AfterStart; } } break; case ElementEdge.AfterStart: if (gravity == LogicalDirection.Forward) { newNode = node.GetFirstContainedNode(); newEdge = ElementEdge.BeforeStart; if (newNode == null) { newNode = node; newEdge = ElementEdge.BeforeEnd; } } break; case ElementEdge.BeforeEnd: if (gravity == LogicalDirection.Backward) { newNode = node.GetLastContainedNode(); newEdge = ElementEdge.AfterEnd; if (newNode == null) { newNode = node; newEdge = ElementEdge.AfterStart; } } break; case ElementEdge.AfterEnd: if (gravity == LogicalDirection.Forward) { newNode = node.GetNextNode(); newEdge = ElementEdge.BeforeStart; if (newNode == null) { newNode = node.GetContainingNode(); newEdge = ElementEdge.BeforeEnd; } } break; } node = (TextTreeNode)newNode; edge = newEdge; }
// Inc/decs the position ref counts on TextTreeTextNodes as the navigator // is repositioned. // If the new ref is to a TextTreeTextNode, the node may be split. // Returns the actual node referenced, which will always be newNode, // unless newNode is a TextTreeTextNode that gets split. The caller // should use the returned node to position navigators. private TextTreeNode AdjustRefCounts(TextTreeNode newNode, ElementEdge newNodeEdge, TextTreeNode oldNode, ElementEdge oldNodeEdge) { TextTreeNode node; // This test should walk the tree upwards to catch all errors...probably not worth the slowdown though. Invariant.Assert(oldNode.ParentNode == null || oldNode.IsChildOfNode(oldNode.ParentNode), "Trying to add ref a dead node!"); Invariant.Assert(newNode.ParentNode == null || newNode.IsChildOfNode(newNode.ParentNode), "Trying to add ref a dead node!"); node = newNode; if (newNode != oldNode || newNodeEdge != oldNodeEdge) { node = newNode.IncrementReferenceCount(newNodeEdge); oldNode.DecrementReferenceCount(oldNodeEdge); } return node; }
// Called by the TextPointer ctor. Initializes this instance. private void Initialize(TextContainer tree, TextTreeNode node, ElementEdge edge, LogicalDirection gravity, uint generation, bool caretUnitBoundaryCache, bool isCaretUnitBoundaryCacheValid, uint layoutGeneration) { _tree = tree; // Fixup of the target node based on gravity. // Positions always cling to a node edge that matches their gravity, // so that insert ops never affect the position. RepositionForGravity(ref node, ref edge, gravity); SetNodeAndEdge(node.IncrementReferenceCount(edge), edge); _generation = generation; this.CaretUnitBoundaryCache = caretUnitBoundaryCache; this.IsCaretUnitBoundaryCacheValid = isCaretUnitBoundaryCacheValid; _layoutGeneration = layoutGeneration; VerifyFlags(); tree.AssertTree(); AssertState(); }
// Returns the symbol type preceding thisPosition. internal static TextPointerContext GetPointerContextBackward(TextTreeNode node, ElementEdge edge) { TextPointerContext symbolType; TextTreeNode previousNode; TextTreeNode lastChildNode; switch (edge) { case ElementEdge.BeforeStart: previousNode = (TextTreeNode)node.GetPreviousNode(); if (previousNode != null) { symbolType = previousNode.GetPointerContext(LogicalDirection.Backward); } else { // The root node is special, there's no ElementStart/End, so test for null parent. Invariant.Assert(node.GetContainingNode() != null, "Bad position!"); // Illegal to be at root BeforeStart. symbolType = (node.GetContainingNode() is TextTreeRootNode) ? TextPointerContext.None : TextPointerContext.ElementStart; } break; case ElementEdge.AfterStart: // The root node is special, there's no ElementStart/End, so test for null parent. Invariant.Assert(node.ParentNode != null || node is TextTreeRootNode, "Inconsistent node.ParentNode"); symbolType = (node.ParentNode != null) ? TextPointerContext.ElementStart : TextPointerContext.None; break; case ElementEdge.BeforeEnd: lastChildNode = (TextTreeNode)node.GetLastContainedNode(); if (lastChildNode != null) { symbolType = lastChildNode.GetPointerContext(LogicalDirection.Backward); } else { goto case ElementEdge.AfterStart; } break; case ElementEdge.AfterEnd: symbolType = node.GetPointerContext(LogicalDirection.Backward); break; default: Invariant.Assert(false, "Unknown ElementEdge value"); symbolType = TextPointerContext.Text; break; } return symbolType; }
internal static TextPointerContext GetPointerContextForward(TextTreeNode node, ElementEdge edge) { TextTreeNode nextNode; TextTreeNode firstContainedNode; TextPointerContext symbolType; switch (edge) { case ElementEdge.BeforeStart: symbolType = node.GetPointerContext(LogicalDirection.Forward); break; case ElementEdge.AfterStart: if (node.ContainedNode != null) { firstContainedNode = (TextTreeNode)node.GetFirstContainedNode(); symbolType = firstContainedNode.GetPointerContext(LogicalDirection.Forward); } else { goto case ElementEdge.BeforeEnd; } break; case ElementEdge.BeforeEnd: // The root node is special, there's no ElementStart/End, so test for null parent. Invariant.Assert(node.ParentNode != null || node is TextTreeRootNode, "Inconsistent node.ParentNode"); symbolType = (node.ParentNode != null) ? TextPointerContext.ElementEnd : TextPointerContext.None; break; case ElementEdge.AfterEnd: nextNode = (TextTreeNode)node.GetNextNode(); if (nextNode != null) { symbolType = nextNode.GetPointerContext(LogicalDirection.Forward); } else { // The root node is special, there's no ElementStart/End, so test for null parent. Invariant.Assert(node.GetContainingNode() != null, "Bad position!"); // Illegal to be at root AfterEnd. symbolType = (node.GetContainingNode() is TextTreeRootNode) ? TextPointerContext.None : TextPointerContext.ElementEnd; } break; default: Invariant.Assert(false, "Unreachable code."); symbolType = TextPointerContext.Text; break; } return symbolType; }
// Finds the previous run, returned as a node/edge pair. // Returns false if there is no preceding run, in which case node/edge will match the input position. // The returned node/edge pair respects the input positon's gravity. internal static bool GetPreviousNodeAndEdge(TextTreeNode sourceNode, ElementEdge sourceEdge, bool plainTextOnly, out TextTreeNode node, out ElementEdge edge) { SplayTreeNode currentNode; SplayTreeNode newNode; SplayTreeNode containingNode; bool startedAdjacentToTextNode; bool endedAdjacentToTextNode; node = sourceNode; edge = sourceEdge; newNode = node; currentNode = node; // If we started next to a TextTreeTextNode, and the next node // is also a TextTreeTextNode, then skip past the second node // as well -- multiple text nodes count as a single Move run. do { startedAdjacentToTextNode = false; endedAdjacentToTextNode = false; switch (edge) { case ElementEdge.BeforeStart: newNode = currentNode.GetPreviousNode(); if (newNode != null) { // Move to next node/last child; if (newNode is TextTreeTextElementNode) { // Move to previous node last child/previous node edge = ElementEdge.BeforeEnd; } else { // Move to previous previous node/previous node. startedAdjacentToTextNode = newNode is TextTreeTextNode; endedAdjacentToTextNode = startedAdjacentToTextNode && newNode.GetPreviousNode() is TextTreeTextNode; } } else { containingNode = currentNode.GetContainingNode(); if (!(containingNode is TextTreeRootNode)) { // Move to parent. newNode = containingNode; } } break; case ElementEdge.AfterStart: newNode = currentNode.GetPreviousNode(); if (newNode != null) { endedAdjacentToTextNode = newNode is TextTreeTextNode; // Move to previous node; edge = ElementEdge.AfterEnd; } else { // Move to inner edge of parent. newNode = currentNode.GetContainingNode(); } break; case ElementEdge.BeforeEnd: newNode = currentNode.GetLastContainedNode(); if (newNode != null) { // Move to penultimate child/last child or inner edge of last child. if (newNode is TextTreeTextElementNode) { edge = ElementEdge.BeforeEnd; } else { startedAdjacentToTextNode = newNode is TextTreeTextNode; endedAdjacentToTextNode = startedAdjacentToTextNode && newNode.GetPreviousNode() is TextTreeTextNode; edge = ElementEdge.BeforeStart; } } else if (currentNode is TextTreeTextElementNode) { // Move to next node. newNode = currentNode; edge = ElementEdge.BeforeStart; } else { Invariant.Assert(currentNode is TextTreeRootNode, "currentNode is expected to be a TextTreeRootNode"); // This is the root node, leave newNode null. } break; case ElementEdge.AfterEnd: newNode = currentNode.GetLastContainedNode(); if (newNode != null) { // Move to inner edge/last child. } else if (currentNode is TextTreeTextElementNode) { // Move to opposite edge. newNode = currentNode; edge = ElementEdge.AfterStart; } else { // Move to previous node. startedAdjacentToTextNode = currentNode is TextTreeTextNode; edge = ElementEdge.AfterStart; goto case ElementEdge.AfterStart; } break; default: Invariant.Assert(false, "Unknown ElementEdge value"); break; } currentNode = newNode; // Multiple text nodes count as a single Move run. // Instead of iterating through N text nodes, exploit // the fact (when we can) that text nodes are only ever contained in // runs with no other content. Jump straight to the start. if (startedAdjacentToTextNode && endedAdjacentToTextNode && plainTextOnly) { newNode = newNode.GetContainingNode(); Invariant.Assert(newNode is TextTreeRootNode); if (edge == ElementEdge.AfterEnd) { edge = ElementEdge.AfterStart; } else { newNode = newNode.GetFirstContainedNode(); Invariant.Assert(newNode != null); Invariant.Assert(edge == ElementEdge.BeforeStart); } break; } } while (startedAdjacentToTextNode && endedAdjacentToTextNode); if (newNode != null) { node = (TextTreeNode)newNode; } return (newNode != null); }
// Dumps a node and all its children. internal static void Dump(TextTreeNode node) { DumpNodeRecursive(node, 0); }
internal bool GetPreviousNodeAndEdge(out TextTreeNode node, out ElementEdge edge) { DebugAssertGeneration(); return GetPreviousNodeAndEdge(_node, this.Edge, _tree.PlainTextOnly, out node, out edge); }
// Positions this navigator at a node/edge pair. // Node/edge are adjusted based on the current gravity. private void MoveToNode(TextContainer tree, TextTreeNode node, ElementEdge edge) { RepositionForGravity(ref node, ref edge, GetGravityInternal()); _tree = tree; SetNodeAndEdge(AdjustRefCounts(node, edge, _node, this.Edge), edge); _generation = tree.PositionGeneration; }
// Fires Change events for IMELeftEdgeCharCount deltas to a node after it // moves into position such that it is the leftmost child // of its parent. private void RaiseEventForNewFirstIMEVisibleNode(TextTreeNode node) { TextPointer startEdgePosition = new TextPointer(this, node, ElementEdge.BeforeStart); // node was the old second node. Its IMECharCount // just dropped down, report that. AddChange(startEdgePosition, /* symbolCount */ 0, /* IMECharCount */ 1, PrecursorTextChangeType.ContentRemoved); }
// Repositions the TextPointer and clears any relevant caches. private void SetNodeAndEdge(TextTreeNode node, ElementEdge edge) { Invariant.Assert(edge == ElementEdge.BeforeStart || edge == ElementEdge.AfterStart || edge == ElementEdge.BeforeEnd || edge == ElementEdge.AfterEnd); _node = node; _flags = (_flags & ~(uint)Flags.EdgeMask) | (uint)edge; VerifyFlags(); // Always clear the caret unit boundary cache when we move to a new position. this.IsCaretUnitBoundaryCacheValid = false; }
internal static TextTreeNode GetScopingNode(TextTreeNode node, ElementEdge edge) { TextTreeNode scopingNode; switch (edge) { case ElementEdge.BeforeStart: case ElementEdge.AfterEnd: scopingNode = (TextTreeNode)node.GetContainingNode(); break; case ElementEdge.AfterStart: case ElementEdge.BeforeEnd: default: scopingNode = node; break; } return scopingNode; }
// Finds a node/edge pair matching a given char offset in the tree. // If the pair matches a character within a text node, the text node is split. internal void GetNodeAndEdgeAtCharOffset(int charOffset, out TextTreeNode node, out ElementEdge edge) { int nodeCharOffset; int siblingTreeCharOffset; bool checkZeroWidthNode; // Offset zero/SymbolCount-1 are before/after the root node, which // is an illegal position -- you can't add or remove content there // and it's never exposed publicly. Invariant.Assert(charOffset >= 0 && charOffset <= this.IMECharCount, "Bogus char offset!"); if (this.IMECharCount == 0) { node = null; edge = ElementEdge.BeforeStart; return; } // If this flag is set true on exit, we need to consider the case // where we've found a "zero-width" (SymbolCount == 0) text node. // Zero width nodes needs special handling, since they are logically // part of a following or preceding node. checkZeroWidthNode = false; // Find the node. node = _rootNode; nodeCharOffset = 0; // Each iteration walks through one tree. while (true) { int leftEdgeCharCount = 0; TextTreeTextElementNode textElementNode = node as TextTreeTextElementNode; if (textElementNode != null) { leftEdgeCharCount = textElementNode.IMELeftEdgeCharCount; if (leftEdgeCharCount > 0) { if (charOffset == nodeCharOffset) { edge = ElementEdge.BeforeStart; break; } if (charOffset == nodeCharOffset + leftEdgeCharCount) { edge = ElementEdge.AfterStart; break; } } } else if (node is TextTreeTextNode || node is TextTreeObjectNode) { if (charOffset == nodeCharOffset) { edge = ElementEdge.BeforeStart; checkZeroWidthNode = true; break; } if (charOffset == nodeCharOffset + node.IMECharCount) { edge = ElementEdge.AfterEnd; checkZeroWidthNode = true; break; } } // No child node? That means we're inside a TextTreeTextNode. if (node.ContainedNode == null) { Invariant.Assert(node is TextTreeTextNode); // Need to split the TextTreeTextNode. // Here we want a character buried inside a single node, split // the node open.... node = ((TextTreeTextNode)node).Split(charOffset - nodeCharOffset, ElementEdge.AfterEnd); edge = ElementEdge.BeforeStart; break; } // Need to look into one of the child nodes. node = (TextTreeNode)node.ContainedNode; nodeCharOffset += leftEdgeCharCount; // Skip over the parent element start edge. // Walk down the sibling tree. node = (TextTreeNode)node.GetSiblingAtCharOffset(charOffset - nodeCharOffset, out siblingTreeCharOffset); nodeCharOffset += siblingTreeCharOffset; } // If we're on a zero-width TextTreeTextNode we need some special handling. if (checkZeroWidthNode) { node = (TextTreeNode)AdjustForZeroWidthNode(node, edge); } }
// Does a deep extract of all top-level TextElements between two positions. // Returns the combined symbol count of all extracted elements. // Each extracted element (and its children) are moved into a private tree. // This insures that outside references to the TextElement can still use // the TextElements freely, inserting or removing content, etc. // // Also calls AddLogicalChild on any top-level UIElements encountered. private int CutTopLevelLogicalNodes(TextTreeNode containingNode, TextPointer startPosition, TextPointer endPosition, out int charCount) { SplayTreeNode node; SplayTreeNode nextNode; SplayTreeNode stopNode; TextTreeTextElementNode elementNode; TextTreeObjectNode uiElementNode; char[] elementText; int symbolCount; TextContainer tree; TextPointer newTreeStart; DependencyObject logicalParent; object currentLogicalChild; Invariant.Assert(startPosition.GetScopingNode() == endPosition.GetScopingNode(), "startPosition/endPosition not in same sibling tree!"); node = startPosition.GetAdjacentSiblingNode(LogicalDirection.Forward); stopNode = endPosition.GetAdjacentSiblingNode(LogicalDirection.Forward); symbolCount = 0; charCount = 0; logicalParent = containingNode.GetLogicalTreeNode(); while (node != stopNode) { currentLogicalChild = null; // Get the next node now, before we extract any TextElementNodes. nextNode = node.GetNextNode(); elementNode = node as TextTreeTextElementNode; if (elementNode != null) { // Grab the IMECharCount before we modify the node. // This value depends on the node's current context. int imeCharCountInOriginalContainer = elementNode.IMECharCount; // Cut and record the matching symbols. elementText = TextTreeText.CutText(_rootNode.RootTextBlock, elementNode.GetSymbolOffset(this.Generation), elementNode.SymbolCount); // Rip the element out of its sibling tree. // textElementNode.TextElement's TextElementNode will be updated // with a deep copy of all contained nodes. We need a deep copy // to ensure the new element/tree has no TextPointer references. ExtractElementFromSiblingTree(containingNode, elementNode, true /* deep */); // Assert that the TextElement now points to a new TextElementNode, not the original one. Invariant.Assert(elementNode.TextElement.TextElementNode != elementNode); // We want to start referring to the copied node, update elementNode. elementNode = elementNode.TextElement.TextElementNode; UpdateContainerSymbolCount(containingNode, -elementNode.SymbolCount, -imeCharCountInOriginalContainer); NextGeneration(true /* deletedContent */); // Stick it in a private tree so it's safe for the outside world to play with. tree = new TextContainer(null, false /* plainTextOnly */); newTreeStart = tree.Start; tree.InsertElementToSiblingTree(newTreeStart, newTreeStart, elementNode); Invariant.Assert(elementText.Length == elementNode.SymbolCount); tree.UpdateContainerSymbolCount(elementNode.GetContainingNode(), elementNode.SymbolCount, elementNode.IMECharCount); tree.DemandCreateText(); TextTreeText.InsertText(tree.RootTextBlock, 1 /* symbolOffset */, elementText); tree.NextGeneration(false /* deletedContent */); currentLogicalChild = elementNode.TextElement; // Keep a running total of how many symbols we've removed. symbolCount += elementNode.SymbolCount; charCount += imeCharCountInOriginalContainer; } else { uiElementNode = node as TextTreeObjectNode; if (uiElementNode != null) { currentLogicalChild = uiElementNode.EmbeddedElement; } } // Remove the child from the logical tree LogicalTreeHelper.RemoveLogicalChild(logicalParent, currentLogicalChild); node = nextNode; } if (symbolCount > 0) { startPosition.SyncToTreeGeneration(); endPosition.SyncToTreeGeneration(); } return symbolCount; }
// Increments the position reference counts on nodes immediately // preceding and following a delete operation. // // Whenever we delete a span of content, we have to worry about any // positions still referencing the deleted content. They have enough // information to find their way back to the surrounding nodes, but // we need to increment the ref count on those nodes now so that they'll // still be around when the positions need them. // // Because incrementing a ref count on a text node edge may involve // splitting the text node, this method takes refs to nodes and will // update the refs if a node is split. // // Called by DeleteContentFromSiblingTree and ExtractElementInternal. private void AdjustRefCountsForContentDelete(ref TextTreeNode previousNode, ElementEdge previousEdge, ref TextTreeNode nextNode, ElementEdge nextEdge, TextTreeNode middleSubTree) { bool leftEdgeReferenceCount; bool rightEdgeReferenceCount; leftEdgeReferenceCount = false; rightEdgeReferenceCount = false; // Get the count of all positions referencing text node edges across the deleted content. GetReferenceCounts((TextTreeNode)middleSubTree.GetMinSibling(), ref leftEdgeReferenceCount, ref rightEdgeReferenceCount); previousNode = previousNode.IncrementReferenceCount(previousEdge, rightEdgeReferenceCount); nextNode = nextNode.IncrementReferenceCount(nextEdge, leftEdgeReferenceCount); }
// Returns the TextTreeTextNode in the direction indicated bordering // a TextPointer, or null if no such node exists. internal static TextTreeTextNode GetAdjacentTextNodeSibling(TextTreeNode node, ElementEdge edge, LogicalDirection direction) { return GetAdjacentSiblingNode(node, edge, direction) as TextTreeTextNode; }
//------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods // Walks the tree from node to the end of its sibling list, // copying content along the way, // halting when/if haltNode is encountered. haltNode may be // null, in which case we walk to the end of the sibling list. // // Returns a ContentContainer holding a deep copy of all content // walked. // // This method is called recursively when TextElement nodes // are encountered. private ContentContainer CopyContent(TextTreeNode node, TextTreeNode haltNode) { ContentContainer firstContainer; ContentContainer container; ContentContainer nextContainer; TextTreeTextNode textNode; TextTreeObjectNode objectNode; TextTreeTextElementNode elementNode; firstContainer = null; container = null; while (node != haltNode && node != null) { textNode = node as TextTreeTextNode; if (textNode != null) { node = CopyTextNode(textNode, haltNode, out nextContainer); } else { objectNode = node as TextTreeObjectNode; if (objectNode != null) { node = CopyObjectNode(objectNode, out nextContainer); } else { Invariant.Assert(node is TextTreeTextElementNode, "Unexpected TextTreeNode type!"); elementNode = (TextTreeTextElementNode)node; node = CopyElementNode(elementNode, out nextContainer); } } if (container == null) { firstContainer = nextContainer; } else { container.NextContainer = nextContainer; } container = nextContainer; } return firstContainer; }
// Increments the position reference counts on nodes immediately // preceding and following a delete operation on a single TextElementNode. // This is similar to AdjustRefCountsForContentDelete, except that // in this case we deleting a single node, and positions at the // BeforeStart/AfterEnd edges may move into contained content, which // is still live in the tree. // // Whenever we delete a span of content, we have to worry about any // positions still referencing the deleted content. They have enough // information to find their way back to the surrounding nodes, but // we need to increment the ref count on those nodes now so that they'll // still be around when the positions need them. // // Because incrementing a ref count on a text node edge may involve // splitting the text node, this method takes refs to nodes and will // update the refs if a node is split. // // Called by ExtractElementFromSiblingTree. private void AdjustRefCountsForShallowDelete(ref TextTreeNode previousNode, ElementEdge previousEdge, ref TextTreeNode nextNode,ElementEdge nextEdge, ref TextTreeNode firstContainedNode, ref TextTreeNode lastContainedNode, TextTreeTextElementNode extractedElementNode) { previousNode = previousNode.IncrementReferenceCount(previousEdge, extractedElementNode.AfterStartReferenceCount); nextNode = nextNode.IncrementReferenceCount(nextEdge, extractedElementNode.BeforeEndReferenceCount); if (firstContainedNode != null) { firstContainedNode = firstContainedNode.IncrementReferenceCount(ElementEdge.BeforeStart, extractedElementNode.BeforeStartReferenceCount); } else { nextNode = nextNode.IncrementReferenceCount(nextEdge, extractedElementNode.BeforeStartReferenceCount); } if (lastContainedNode != null) { lastContainedNode = lastContainedNode.IncrementReferenceCount(ElementEdge.AfterEnd, extractedElementNode.AfterEndReferenceCount); } else { previousNode = previousNode.IncrementReferenceCount(previousEdge, extractedElementNode.AfterEndReferenceCount); } }
// Returns a copy of a sibling tree. node is expected to be the first sibling. private TextTreeNode DeepCopyContainedNodes(TextTreeNode node) { TextTreeNode rootClone; TextTreeNode previousClone; TextTreeNode clone; TextTreeTextElementNode elementNode; rootClone = null; previousClone = null; do { elementNode = node as TextTreeTextElementNode; if (elementNode != null) { clone = DeepCopy(elementNode); } else { clone = node.Clone(); } // clone will be null in one case: if we're trying to clone an // empty TextNode. We can skip empty TextNodes (symbol count == 0) // because we know the clones have no TextPointer references, so // an empty node serves no purpose. Invariant.Assert(clone != null || node is TextTreeTextNode && node.SymbolCount == 0); if (clone != null) { clone.ParentNode = previousClone; if (previousClone != null) { previousClone.RightChildNode = clone; } else { Invariant.Assert(clone.Role == SplayTreeNodeRole.LocalRoot); // Remember the first clone created. rootClone = clone; } previousClone = clone; } node = (TextTreeNode)node.GetNextNode(); } while (node != null); return rootClone; }
internal static TextTreeNode GetAdjacentSiblingNode(TextTreeNode node, ElementEdge edge, LogicalDirection direction) { SplayTreeNode sibling; if (direction == LogicalDirection.Forward) { switch (edge) { case ElementEdge.BeforeStart: sibling = node; break; case ElementEdge.AfterStart: sibling = node.GetFirstContainedNode(); break; case ElementEdge.BeforeEnd: default: sibling = null; break; case ElementEdge.AfterEnd: sibling = node.GetNextNode(); break; } } else // direction == LogicalDirection.Backward { switch (edge) { case ElementEdge.BeforeStart: sibling = node.GetPreviousNode(); break; case ElementEdge.AfterStart: default: sibling = null; break; case ElementEdge.BeforeEnd: sibling = node.GetLastContainedNode(); break; case ElementEdge.AfterEnd: sibling = node; break; } } return (TextTreeNode)sibling; }
private void AssertTreeRecursive(TextTreeNode containingNode) { TextTreeNode node; if (containingNode.ContainedNode == null) { Invariant.Assert(containingNode.ParentNode == null || containingNode.ParentNode.ContainedNode == containingNode); return; } Invariant.Assert(containingNode.ContainedNode.ParentNode == containingNode); for (node = (TextTreeNode)containingNode.ContainedNode.GetMinSibling(); node != null; node = (TextTreeNode)node.GetNextNode()) { if (node != containingNode.ContainedNode) { Invariant.Assert(node.ParentNode.LeftChildNode == node || node.ParentNode.RightChildNode == node); } Invariant.Assert(node.SymbolCount >= 0); if (node.SymbolCount == 0) { Invariant.Assert(node is TextTreeTextNode); Invariant.Assert(node.BeforeStartReferenceCount > 0 || node.AfterEndReferenceCount > 0); } if (node.ContainedNode != null) { AssertTreeRecursive((TextTreeNode)node.ContainedNode); } } }
// Sums the reference counts for a node and all following or contained nodes. private void GetReferenceCounts(TextTreeNode node, ref bool leftEdgeReferenceCount, ref bool rightEdgeReferenceCount) { do { // We can combine BeforeStart/BeforeEnd and AfterStart/AfterEnd because // they include all positions with equal gravity. leftEdgeReferenceCount |= node.BeforeStartReferenceCount || node.BeforeEndReferenceCount; rightEdgeReferenceCount |= node.AfterStartReferenceCount || node.AfterEndReferenceCount; if (node.ContainedNode != null) { GetReferenceCounts((TextTreeNode)node.ContainedNode.GetMinSibling(), ref leftEdgeReferenceCount, ref rightEdgeReferenceCount); } node = (TextTreeNode)node.GetNextNode(); } while (node != null); }
// Creates a new TextPointer instance. internal TextPointer(TextContainer tree, TextTreeNode node, ElementEdge edge, LogicalDirection direction) { Initialize(tree, node, edge, direction, tree.PositionGeneration, false, false, tree.LayoutGeneration); }