// The InsertText worker. Adds text to the tree at a specified position. // text is either a string or char[] to insert. internal void InsertTextInternal(TextPointer position, object text) { TextTreeTextNode textNode; SplayTreeNode containingNode; TextPointer originalPosition; int symbolOffset; int textLength; LogicalDirection direction; Invariant.Assert(text is string || text is char[], "Unexpected type for 'text' parameter!"); textLength = GetTextLength(text); if (textLength == 0) return; DemandCreateText(); position.SyncToTreeGeneration(); if (Invariant.Strict) { if (position.Node.SymbolCount == 0) { // We expect only TextTreeTextNodes ever have zero symbol counts. // This can happen in two cases: // // <TextNode referencedEdge=BeforeStart symbolCount=1+/> <TextNode referencedEdge=AfterEnd symbolCount=0/> // or // <TextNode referencedEdge=BeforeStart symbolCount=0/> <TextNode referencedEdge=AfterEnd symbolCount=1+/> // Invariant.Assert(position.Node is TextTreeTextNode); Invariant.Assert((position.Edge == ElementEdge.AfterEnd && position.Node.GetPreviousNode() is TextTreeTextNode && position.Node.GetPreviousNode().SymbolCount > 0) || (position.Edge == ElementEdge.BeforeStart && position.Node.GetNextNode() is TextTreeTextNode && position.Node.GetNextNode().SymbolCount > 0)); } } BeforeAddChange(); // During document load we won't have listeners and we can save // an allocation on every insert. This can easily save 1000's of allocations during boot. originalPosition = this.HasListeners ? new TextPointer(position, LogicalDirection.Backward) : null; // Find a bordering TextTreeTextNode, if any. // We know position already points to the current TextNode, if there is one, so // we can't append text to that node (it would disrespect position's gravity to do so). // So we either have to find a neighboring text node with no position references, or // create a new node. // Look for a bordering text node. if (position.Edge == ElementEdge.BeforeStart || position.Edge == ElementEdge.BeforeEnd) { direction = LogicalDirection.Backward; } else { direction = LogicalDirection.Forward; } textNode = position.GetAdjacentTextNodeSibling(direction); if (textNode != null) { // We can't use a text node that is already referred to by text positions. // Doing so could displace the positions, since they expect to remain at // the node edge no matter what happens. if ((direction == LogicalDirection.Backward && textNode.AfterEndReferenceCount) || (direction == LogicalDirection.Forward && textNode.BeforeStartReferenceCount)) { textNode = null; } } if (textNode == null) { // No text node available. Create and insert one. textNode = new TextTreeTextNode(); textNode.InsertAtPosition(position); containingNode = textNode.GetContainingNode(); } else { // We didn't insert a new node, so splay textNode to the root so // we don't invalidate any LeftSymbolCounts of ancestor nodes. textNode.Splay(); containingNode = textNode.ParentNode; } // Update the symbol counts. textNode.SymbolCount += textLength; // This simultaneously updates textNode.IMECharCount. UpdateContainerSymbolCount(containingNode, /* symbolCount */ textLength, /* charCount */ textLength); // Insert the raw text. symbolOffset = textNode.GetSymbolOffset(this.Generation); TextTreeText.InsertText(_rootNode.RootTextBlock, symbolOffset, text); // Handle undo. TextTreeUndo.CreateInsertUndoUnit(this, symbolOffset, textLength); // Announce the change. NextGeneration(false /* deletedContent */); AddChange(originalPosition, /* symbolCount */ textLength, /* charCount */ textLength, PrecursorTextChangeType.ContentAdded); // Notify the TextElement of a content change. TextElement textElement = position.Parent as TextElement; if (textElement != null) { textElement.OnTextUpdated(); } }