/// <summary>Gets the new anchor position as a consequence of this document change</summary> public int NewOffset(int offset, AnchorMapping.EMove move_type) { if (offset > Offset) { offset -= Math.Min(offset - Offset, TextRemoved.Length - Offset); } if (offset >= Offset) { offset += move_type switch { AnchorMapping.EMove.Default => offset > Offset ? TextInserted.Length : 0, AnchorMapping.EMove.BeforeInsertion => offset > Offset ? TextInserted.Length : 0, AnchorMapping.EMove.AfterInsertion => TextInserted.Length, _ => throw new Exception("Unknown anchor movement type") }; } return(offset); } }
// Notes: // - TextAnchor references an offset (a position between two characters). It automatically updates // when text is inserted/removed in the document. // - TextAnchor only adds a weak reference to document.Change so unsubscribing isn't needed and // anchors wont keep a Document from being collected. // - To track a segment, use AnchorSegment which implements ISegment using two text anchors. // - Use TextDocument.CreateAnchor to create an anchor from an offset. // - Anchor movement is ambiguous if text is inserted exactly at the anchor's location. Does the anchor // stay before the inserted text, or does it move after it? The property MovementType is used to determine // which of these options the anchor will choose. // // Example: // auto anchor = document.CreateAnchor(offset); // ChangeMyDocument(); // int newOffset = anchor.Offset; internal TextAnchor(TextDocument document, int offset, AnchorMapping.EMove move) { Offset = offset; Movement = move; document.ChangeInternal += WeakRef.MakeWeak <DocumentChangeEventArgs>(HandleDocChanged, h => document.ChangeInternal -= h); void HandleDocChanged(object?sender, DocumentChangeEventArgs e) { if (!(sender is TextDocument doc)) { throw new Exception("sender should be a TextDocument"); } if (e.After && !IsDeleted) { // If the anchor is before the change, ignore if (Offset < e.Offset) { } // If the anchor is after the removed text, adjust by the difference in inserted - removed else if (Offset >= e.Offset + e.TextRemoved.Length) { Offset += e.TextInserted.Length - e.TextRemoved.Length; } // Otherwise, the anchor is within the removed text else { switch (Movement) { case AnchorMapping.EMove.Default: { // If the anchor is at the insertion offset, move to after the inserted text if (Offset == e.Offset) { Offset += e.TextInserted.Length - e.TextRemoved.Length; } // Otherwise, delete the anchor else { Delete(); } break; } case AnchorMapping.EMove.BeforeInsertion: { // Move the anchor to the insertion offset Offset += e.Offset; break; } case AnchorMapping.EMove.AfterInsertion: { // Move the anchor to after the insertion Offset += e.TextInserted.Length - e.TextRemoved.Length; break; } default: { throw new Exception("Unknown anchor movement type"); } } } } } }