// 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("/>"); } }
// 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; }
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); }
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; }