public TextAnchor CreateAnchor(int offset) { Log("CreateAnchor(" + offset + ")"); TextAnchor anchor = new TextAnchor(document); anchor.node = new TextAnchorNode(anchor); if (root == null) { // creating the first text anchor root = anchor.node; root.totalLength = root.length = offset; } else if (offset >= root.totalLength) { // append anchor at end of tree anchor.node.totalLength = anchor.node.length = offset - root.totalLength; InsertAsRight(root.RightMost, anchor.node); } else { // insert anchor in middle of tree TextAnchorNode n = FindNode(ref offset); Debug.Assert(offset < n.length); // split segment 'n' at offset anchor.node.totalLength = anchor.node.length = offset; n.length -= offset; InsertBefore(n, anchor.node); } DeleteMarkedNodes(); return anchor; }
/// <summary> /// Called when this switch body element is inserted to the editor. /// </summary> public override void Insert(InsertionContext context) { this.context = context; this.context.Deactivated += new EventHandler<SnippetEventArgs>(InteractiveModeCompleted); this.anchor = SetUpAnchorAtInsertion(context); this.classFinderContext = new ClassFinder(ParserService.ParseCurrentViewContent(), Editor.Document.Text, Editor.Caret.Offset); }
public void OnInsertionCompleted() { // anchors must be created in OnInsertionCompleted because they should move only // due to user insertions, not due to insertions of other snippet parts start = context.Document.CreateAnchor(startOffset); start.MovementType = AnchorMovementType.BeforeInsertion; end = context.Document.CreateAnchor(endOffset); end.MovementType = AnchorMovementType.AfterInsertion; start.Deleted += AnchorDeleted; end.Deleted += AnchorDeleted; // Be careful with references from the document to the editing/snippet layer - use weak events // to prevent memory leaks when the text area control gets dropped from the UI while the snippet is active. // The InsertionContext will keep us alive as long as the snippet is in interactive mode. TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this); background = new Renderer { Layer = KnownLayer.Background, element = this }; foreground = new Renderer { Layer = KnownLayer.Text, element = this }; context.TextArea.TextView.BackgroundRenderers.Add(background); context.TextArea.TextView.BackgroundRenderers.Add(foreground); context.TextArea.Caret.PositionChanged += Caret_PositionChanged; Caret_PositionChanged(null, null); this.Text = GetText(); }
public void AnchorsSurviveDeletion() { document.Text = new string(' ', 10); TextAnchor[] a1 = new TextAnchor[11]; TextAnchor[] a2 = new TextAnchor[11]; for (int i = 0; i < 11; i++) { //Console.WriteLine("Insert first at i = " + i); a1[i] = document.CreateAnchor(i); a1[i].SurviveDeletion = true; //Console.WriteLine(document.GetTextAnchorTreeAsString()); //Console.WriteLine("Insert second at i = " + i); a2[i] = document.CreateAnchor(i); a2[i].SurviveDeletion = false; //Console.WriteLine(document.GetTextAnchorTreeAsString()); } for (int i = 0; i < 11; i++) { Assert.AreEqual(i, a1[i].Offset); Assert.AreEqual(i, a2[i].Offset); } document.Remove(1, 8); for (int i = 0; i < 11; i++) { if (i <= 1) { Assert.IsFalse(a1[i].IsDeleted); Assert.IsFalse(a2[i].IsDeleted); Assert.AreEqual(i, a1[i].Offset); Assert.AreEqual(i, a2[i].Offset); } else if (i <= 8) { Assert.IsFalse(a1[i].IsDeleted); Assert.IsTrue(a2[i].IsDeleted); Assert.AreEqual(1, a1[i].Offset); } else { Assert.IsFalse(a1[i].IsDeleted); Assert.IsFalse(a2[i].IsDeleted); Assert.AreEqual(i - 8, a1[i].Offset); Assert.AreEqual(i - 8, a2[i].Offset); } } }
public AnchorAdapter(TextAnchor anchor) { this.anchor = anchor; }
internal int totalLength; // totalLength = length + left.totalLength + right.totalLength public TextAnchorNode(TextAnchor anchor) : base(anchor) { }
public Breakpoint(TextAnchor anchor) { Anchor = anchor; IsEnabled = true; }
internal int totalLength; // totalLength = length + left.totalLength + right.totalLength public TextAnchorNode(TextAnchor anchor) : base(anchor) { }
public void CreateAndMoveAnchors() { List <TextAnchor> anchors = new List <TextAnchor>(); List <int> expectedOffsets = new List <int>(); document.Text = new string(' ', 1000); for (int t = 0; t < 250; t++) { //Console.Write("t = " + t + " "); int c = rnd.Next(50); switch (rnd.Next(5)) { case 0: //Console.WriteLine("Add c=" + c + " anchors"); for (int i = 0; i < c; i++) { int offset = rnd.Next(document.TextLength); TextAnchor anchor = document.CreateAnchor(offset); if (rnd.Next(2) == 0) { anchor.MovementType = AnchorMovementType.BeforeInsertion; } else { anchor.MovementType = AnchorMovementType.AfterInsertion; } anchor.SurviveDeletion = rnd.Next(2) == 0; anchors.Add(anchor); expectedOffsets.Add(offset); } break; case 1: if (c <= anchors.Count) { //Console.WriteLine("Remove c=" + c + " anchors"); anchors.RemoveRange(0, c); expectedOffsets.RemoveRange(0, c); GC.Collect(); } break; case 2: int insertOffset = rnd.Next(document.TextLength); int insertLength = rnd.Next(1000); //Console.WriteLine("insertOffset=" + insertOffset + " insertLength="+insertLength); document.Insert(insertOffset, new string(' ', insertLength)); for (int i = 0; i < anchors.Count; i++) { if (anchors[i].MovementType == AnchorMovementType.BeforeInsertion) { if (expectedOffsets[i] > insertOffset) { expectedOffsets[i] += insertLength; } } else { if (expectedOffsets[i] >= insertOffset) { expectedOffsets[i] += insertLength; } } } break; case 3: int removalOffset = rnd.Next(document.TextLength); int removalLength = rnd.Next(document.TextLength - removalOffset); //Console.WriteLine("RemovalOffset=" + removalOffset + " RemovalLength="+removalLength); document.Remove(removalOffset, removalLength); for (int i = anchors.Count - 1; i >= 0; i--) { if (expectedOffsets[i] > removalOffset && expectedOffsets[i] < removalOffset + removalLength) { if (anchors[i].SurviveDeletion) { expectedOffsets[i] = removalOffset; } else { Assert.IsTrue(anchors[i].IsDeleted); anchors.RemoveAt(i); expectedOffsets.RemoveAt(i); } } else if (expectedOffsets[i] > removalOffset) { expectedOffsets[i] -= removalLength; } } break; case 4: int replaceOffset = rnd.Next(document.TextLength); int replaceRemovalLength = rnd.Next(document.TextLength - replaceOffset); int replaceInsertLength = rnd.Next(1000); //Console.WriteLine("ReplaceOffset=" + replaceOffset + " RemovalLength="+replaceRemovalLength + " InsertLength=" + replaceInsertLength); document.Replace(replaceOffset, replaceRemovalLength, new string(' ', replaceInsertLength)); for (int i = anchors.Count - 1; i >= 0; i--) { if (expectedOffsets[i] > replaceOffset && expectedOffsets[i] < replaceOffset + replaceRemovalLength) { if (anchors[i].SurviveDeletion) { if (anchors[i].MovementType == AnchorMovementType.AfterInsertion) { expectedOffsets[i] = replaceOffset + replaceInsertLength; } else { expectedOffsets[i] = replaceOffset; } } else { Assert.IsTrue(anchors[i].IsDeleted); anchors.RemoveAt(i); expectedOffsets.RemoveAt(i); } } else if (expectedOffsets[i] > replaceOffset) { expectedOffsets[i] += replaceInsertLength - replaceRemovalLength; } else if (expectedOffsets[i] == replaceOffset && replaceRemovalLength == 0 && anchors[i].MovementType == AnchorMovementType.AfterInsertion) { expectedOffsets[i] += replaceInsertLength - replaceRemovalLength; } } break; } Assert.AreEqual(anchors.Count, expectedOffsets.Count); for (int j = 0; j < anchors.Count; j++) { Assert.AreEqual(expectedOffsets[j], anchors[j].Offset); } } GC.KeepAlive(anchors); }
public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents) { //Log("HandleTextChange(" + entry + ")"); if (entry.RemovalLength == 0) { // This is a pure insertion. // Unlike a replace with removal, a pure insertion can result in nodes at the same location // to split depending on their MovementType. // Thus, we handle this case on a separate code path // (the code below looks like it does something similar, but it can only split // the set of deletion survivors, not all nodes at an offset) InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); return; } // When handling a replacing text change, we need to: // - find all anchors in the deleted segment and delete them / move them to the appropriate // surviving side. // - adjust the segment size between the left and right side int offset = entry.Offset; int remainingRemovalLength = entry.RemovalLength; // if the text change is happening after the last anchor, we don't have to do anything if (root == null || offset >= root.totalLength) { return; } TextAnchorNode node = FindNode(ref offset); TextAnchorNode firstDeletionSurvivor = null; // go forward through the tree and delete all nodes in the removal segment while (node != null && offset + remainingRemovalLength > node.length) { TextAnchor anchor = (TextAnchor)node.Target; if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion)) { if (firstDeletionSurvivor == null) { firstDeletionSurvivor = node; } // This node should be deleted, but it wants to survive. // We'll just remove the deleted length segment, so the node will be positioned // in front of the removed segment. remainingRemovalLength -= node.length - offset; node.length = offset; offset = 0; UpdateAugmentedData(node); node = node.Successor; } else { // delete node TextAnchorNode s = node.Successor; remainingRemovalLength -= node.length; RemoveNode(node); // we already deleted the node, don't delete it twice nodesToDelete.Remove(node); if (anchor != null) { anchor.OnDeleted(delayedEvents); } node = s; } } // 'node' now is the first anchor after the deleted segment. // If there are no anchors after the deleted segment, 'node' is null. // firstDeletionSurvivor was set to the first node surviving deletion. // Because all non-surviving nodes up to 'node' were deleted, the node range // [firstDeletionSurvivor, node) now refers to the set of all deletion survivors. // do the remaining job of the removal if (node != null) { node.length -= remainingRemovalLength; Debug.Assert(node.length >= 0); } if (entry.InsertionLength > 0) { // we are performing a replacement if (firstDeletionSurvivor != null) { // We got deletion survivors which need to be split into BeforeInsertion // and AfterInsertion groups. // Take care that we don't regroup everything at offset, but only the deletion // survivors - from firstDeletionSurvivor (inclusive) to node (exclusive). // This ensures that nodes immediately before or after the replaced segment // stay where they are (independent from their MovementType) PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); } else if (node != null) { // No deletion survivors: // just perform the insertion node.length += entry.InsertionLength; } } if (node != null) { UpdateAugmentedData(node); } DeleteMarkedNodes(); }