/// <summary> /// Compares the age of this checkpoint to the other checkpoint. /// </summary> /// <remarks>This method is thread-safe.</remarks> /// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception> /// <returns>-1 if this checkpoint is older than <paramref name="other"/>. /// 0 if <c>this</c>==<paramref name="other"/>. /// 1 if this checkpoint is newer than <paramref name="other"/>.</returns> public int CompareAge(ChangeTrackingCheckpoint other) { if (other == null) throw new ArgumentNullException(nameof(other)); if (other.documentIdentifier != documentIdentifier) throw new ArgumentException("Checkpoints do not belong to the same document."); // We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1. // This is guaranteed on x86 because so many checkpoints don't fit into memory. return Math.Sign(unchecked(id - other.id)); }
void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap) { if (length == 0 && newText.Length == 0) return; // trying to replace a single character in 'Normal' mode? // for single characters, 'CharacterReplace' mode is equivalent, but more performant // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode) if (length == 1 && newText.Length == 1 && offsetChangeMap == null) offsetChangeMap = OffsetChangeMap.Empty; string removedText = rope.ToString(offset, length); DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap); // fire DocumentChanging event if (Changing != null) Changing(this, args); cachedText = null; // reset cache of complete document text fireTextChanged = true; DelayedEvents delayedEvents = new DelayedEvents(); lock (lockObject) { // create linked list of checkpoints, if required if (currentCheckpoint != null) { currentCheckpoint = currentCheckpoint.Append(args); } // now update the textBuffer and lineTree if (offset == 0 && length == rope.Length) { // optimize replacing the whole document rope.Clear(); rope.InsertText(0, newText); lineManager.Rebuild(); } else { rope.RemoveRange(offset, length); lineManager.Remove(offset, length); #if DEBUG lineTree.CheckProperties(); #endif rope.InsertText(offset, newText); lineManager.Insert(offset, newText); #if DEBUG lineTree.CheckProperties(); #endif } } // update text anchors if (offsetChangeMap == null) { anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents); } else { foreach (OffsetChangeMapEntry entry in offsetChangeMap) { anchorTree.HandleTextChange(entry, delayedEvents); } } // raise delayed events after our data structures are consistent again delayedEvents.RaiseEvents(); // fire DocumentChanged event if (Changed != null) Changed(this, args); }
internal ChangeTrackingCheckpoint CreateChangeTrackingCheckpoint() { lock (lockObject) { if (currentCheckpoint == null) currentCheckpoint = new ChangeTrackingCheckpoint(lockObject); return currentCheckpoint; } }
public ITextSource CreateSnapshot(out ChangeTrackingCheckpoint checkpoint) { lock (lockObject) { if (currentCheckpoint == null) currentCheckpoint = new ChangeTrackingCheckpoint(lockObject); checkpoint = currentCheckpoint; return new RopeTextSource(rope.Clone()); } }
/// <summary> /// Gets whether this checkpoint belongs to the same document as the other checkpoint. /// </summary> public bool BelongsToSameDocumentAs(ChangeTrackingCheckpoint other) { if (other == null) throw new ArgumentNullException("other"); return documentIdentifier == other.documentIdentifier; }
IEnumerable<DocumentChangeEventArgs> GetForwardChanges(ChangeTrackingCheckpoint other) { // Return changes from this(exclusive) to other(inclusive). ChangeTrackingCheckpoint node = this; do { node = node.next; yield return node.value; } while (node != other); }
internal ChangeTrackingCheckpoint Append(DocumentChangeEventArgs change) { Debug.Assert(this.next == null); this.next = new ChangeTrackingCheckpoint(this.documentIdentifier, change, unchecked( this.id + 1 )); return this.next; }
/// <summary> /// Calculates where the offset has moved in the other buffer version. /// </summary> /// <remarks>This method is thread-safe.</remarks> /// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception> public int MoveOffsetTo(ChangeTrackingCheckpoint other, int oldOffset, AnchorMovementType movement) { int offset = oldOffset; foreach (DocumentChangeEventArgs e in GetChangesTo(other)) { offset = e.GetNewOffset(offset, movement); } return offset; }
/// <summary> /// Gets the changes from this checkpoint to the other checkpoint. /// If 'other' is older than this checkpoint, reverse changes are calculated. /// </summary> /// <remarks>This method is thread-safe.</remarks> /// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception> public IEnumerable<DocumentChangeEventArgs> GetChangesTo(ChangeTrackingCheckpoint other) { int result = CompareAge(other); if (result < 0) return GetForwardChanges(other); else if (result > 0) return other.GetForwardChanges(this).Reverse().Select(change => change.Invert()); else return Empty<DocumentChangeEventArgs>.Array; }
public SnapshotVersion(ChangeTrackingCheckpoint checkpoint) { Debug.Assert(checkpoint != null); this.checkpoint = checkpoint; }
public Snapshot(ITextSource textSource, ChangeTrackingCheckpoint checkpoint) : base(textSource) { this.version = new SnapshotVersion(checkpoint); }
void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap) { if (length == 0 && newText.Length == 0) { return; } // trying to replace a single character in 'Normal' mode? // for single characters, 'CharacterReplace' mode is equivalent, but more performant // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode) if (length == 1 && newText.Length == 1 && offsetChangeMap == null) { offsetChangeMap = OffsetChangeMap.Empty; } string removedText = rope.ToString(offset, length); DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap); // fire DocumentChanging event if (Changing != null) { Changing(this, args); } cachedText = null; // reset cache of complete document text fireTextChanged = true; DelayedEvents delayedEvents = new DelayedEvents(); lock (lockObject) { // create linked list of checkpoints, if required if (currentCheckpoint != null) { currentCheckpoint = currentCheckpoint.Append(args); } // now update the textBuffer and lineTree if (offset == 0 && length == rope.Length) { // optimize replacing the whole document rope.Clear(); rope.InsertText(0, newText); lineManager.Rebuild(); } else { rope.RemoveRange(offset, length); lineManager.Remove(offset, length); #if DEBUG lineTree.CheckProperties(); #endif rope.InsertText(offset, newText); lineManager.Insert(offset, newText); #if DEBUG lineTree.CheckProperties(); #endif } } // update text anchors if (offsetChangeMap == null) { anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents); } else { foreach (OffsetChangeMapEntry entry in offsetChangeMap) { anchorTree.HandleTextChange(entry, delayedEvents); } } // raise delayed events after our data structures are consistent again delayedEvents.RaiseEvents(); // fire DocumentChanged event if (Changed != null) { Changed(this, args); } }
internal ChangeTrackingCheckpoint Append(DocumentChangeEventArgs change) { Debug.Assert(this.next == null); this.next = new ChangeTrackingCheckpoint(this.documentIdentifier, change, unchecked (this.id + 1)); return(this.next); }