/// <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);
     }
 }
Example #2
0
        // 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");
                        }
                        }
                    }
                }
            }
        }