sw/inc/IDocumentState.hxx                   |    1 
 sw/inc/crsrsh.hxx                           |   34 ++
 sw/inc/viscrs.hxx                           |    9 
 sw/source/core/crsr/crsrsh.cxx              |  235 ++++++++++++++++---
 sw/source/core/crsr/viscrs.cxx              |   80 ++++--
 sw/source/core/doc/DocumentStateManager.cxx |  346 +++++++++++++++++++++++++++-
 sw/source/core/doc/doccorr.cxx              |   23 +
 sw/source/core/inc/DocumentStateManager.hxx |    1 
 8 files changed, 667 insertions(+), 62 deletions(-)

New commits:
commit 910ccfe6a35c1a039196e29defa7074d35158e3a
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Wed Jan 29 11:38:26 2025 +0100
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Thu May 8 14:45:23 2025 +0200

    LOCRDT sw: yrs peer cursors
    
    Factor out a class sw::VisibleCursorState containing all data required
    for a peer cursor.
    
    Let SwCursorShell::UpdateCursor() etc correct also the peer cursors
    locally in the sw shells, else the cursors will be painted in the wrong
    place; every peer updates its own cursor in the yrs model.
    
    Split SwVisibleCursor::SetPosAndShow() into 2 functions, to find the new
    position, and actually paint things.
    
    Peer cursors are not VCL cursors (they don't need to blink) but painted
    in SwSelPaintRects::Show() in color via drawinglayer.
    
    Change-Id: I21ae8cb3fd76759a10b5c226cf3294a5c9fc6451
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180900
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    Tested-by: Jenkins

diff --git a/sw/inc/IDocumentState.hxx b/sw/inc/IDocumentState.hxx
index bfacc071df9f..31b60078ddf2 100644
--- a/sw/inc/IDocumentState.hxx
+++ b/sw/inc/IDocumentState.hxx
@@ -68,6 +68,7 @@ public:
         = 0;
     virtual void YrsRemoveCommentImpl(rtl::OString const& rCommentId) = 0;
     virtual void YrsRemoveComment(SwPosition const& rPos, rtl::OString const& 
rCommentId) = 0;
+    virtual void YrsNotifyCursorUpdate() = 0;
 #endif
 
 protected:
diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index 5f0317250484..50f00e4d000c 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -143,12 +143,27 @@ std::optional<OUString> ReplaceBackReferences(const 
i18nutil::SearchOptions2& rS
 bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges,
         SwDoc & rDoc, SwPaM const& rDelPam);
 
+struct VisibleCursorState
+{
+    SwRect m_aCharRect;     ///< Char-SRectangle on which the cursor is located
+    Point m_aCursorHeight;  ///< height & offset from visible Cursor
+
+    SwShellCursor* m_pCurrentCursor;    ///< currently active cursor
+    SwVisibleCursor *m_pVisibleCursor;  ///< cursor displayed in view
+    bool m_bSVCursorVis : 1;        ///< SV-Cursor visible/invisible
+    bool m_bOverwriteCursor : 1;    ///< true -> show Overwrite Cursor
+
+    bool IsOverwriteCursor() const { return m_bOverwriteCursor; }
+    void SetOverwriteCursor( bool bFlag ) { m_bOverwriteCursor = bFlag; }
+};
+
 } // namespace sw
 
 class SAL_DLLPUBLIC_RTTI SwCursorShell
     : public SwViewShell
     , public sw::BroadcastingModify
     , public ::sw::IShellCursorSupplier
+    , protected ::sw::VisibleCursorState
 {
     friend class SwCallLink;
     friend class SwVisibleCursor;
@@ -173,8 +188,6 @@ public:
 
 private:
 
-    SwRect  m_aCharRect;          ///< Char-SRectangle on which the cursor is 
located
-    Point   m_aCursorHeight;        ///< height & offset from visible Cursor
     Point   m_aOldRBPos;          ///< Right/Bottom of last VisArea
                                 // (used in Invalidate by Cursor)
 
@@ -184,15 +197,23 @@ private:
                                    format changes at cursor position.*/
     Link<SwCursorShell&,void> m_aGrfArrivedLnk;      ///< Link calls to UI if 
a graphic is arrived
 
-    SwShellCursor* m_pCurrentCursor;      ///< current cursor
     SwShellCursor* m_pStackCursor;      ///< stack for the cursor
-    SwVisibleCursor *m_pVisibleCursor;        ///< the visible cursor
 
     SwBlockCursor *m_pBlockCursor;   ///< interface of cursor for block 
(=rectangular) selection
 
     SwShellTableCursor* m_pTableCursor; /**< table Cursor; only in tables when 
the
                                    selection lays over 2 columns */
 
+#if defined(YRS)
+public:
+    ::std::unordered_map<OString, ::std::unique_ptr<VisibleCursorState>> 
m_PeerCursors;
+    SwVisibleCursor * FindVisibleCursorForPeer(SwSelPaintRects const& rCursor) 
const;
+    void YrsAddCursor(OString const& rId, ::std::optional<SwPosition> const& 
roPoint, ::std::optional<SwPosition> const& roMark, OUString const& rAuthor);
+    void YrsSetCursor(OString const& rId, ::std::optional<SwPosition> const& 
roPoint, ::std::optional<SwPosition> const& roMark);
+    void YrsDelCursor(OString const& rId);
+private:
+#endif
+
     SwNodeIndex* m_pBoxIdx;       ///< for recognizing of the changed
     SwTableBox* m_pBoxPtr;        ///< table row
 
@@ -217,7 +238,6 @@ private:
     int m_nMarkedListLevel;
 
     bool m_bHasFocus : 1;         ///< Shell is "active" in a window
-    bool m_bSVCursorVis : 1;        ///< SV-Cursor visible/invisible
     bool m_bChgCallFlag : 1;      ///< attribute change inside Start- and 
EndAction
     bool m_bVisPortChgd : 1;      ///< in VisPortChg-Call
                                 // (used in Invalidate by the Cursor)
@@ -236,7 +256,6 @@ private:
     bool m_bAutoUpdateCells : 1;  // true -> autoformat cells
     bool m_bBasicHideCursor : 1;    // true -> HideCursor from Basic
     bool m_bSetCursorInReadOnly : 1;// true -> Cursor is allowed in 
ReadOnly-Areas
-    bool m_bOverwriteCursor : 1;    // true -> show Overwrite Cursor
 
     // true -> send accessible events when cursor changes
     // (set to false when using internal-only helper cursor)
@@ -493,9 +512,6 @@ public:
     void ShowCursors( bool bCursorVis );
     void HideCursors();
 
-    bool IsOverwriteCursor() const { return m_bOverwriteCursor; }
-    void SetOverwriteCursor( bool bFlag ) { m_bOverwriteCursor = bFlag; }
-
     bool IsSendAccessibleCursorEvents() const { return 
m_bSendAccessibleCursorEvents; };
     void SetSendAccessibleCursorEvents(bool bEnable) { 
m_bSendAccessibleCursorEvents = bEnable; };
 
diff --git a/sw/inc/viscrs.hxx b/sw/inc/viscrs.hxx
index 2aba4bbd7110..afb5d96c9a4c 100644
--- a/sw/inc/viscrs.hxx
+++ b/sw/inc/viscrs.hxx
@@ -30,6 +30,7 @@
 
 namespace sdr::overlay { class OverlaySelection; }
 
+namespace sw { struct VisibleCursorState; }
 class SwCursorShell;
 class SfxViewShell;
 class SwContentControlButton;
@@ -41,6 +42,7 @@ class SW_DLLPUBLIC SwVisibleCursor
     friend void InitCore();
     friend void FinitCore();
 
+    ::sw::VisibleCursorState const& m_rState;
     const SwCursorShell* m_pCursorShell;
     vcl::Cursor m_aTextCursor;
 
@@ -52,7 +54,7 @@ class SW_DLLPUBLIC SwVisibleCursor
     bool m_bIsDragCursor;
 
 public:
-    SwVisibleCursor( const SwCursorShell * pCShell );
+    SwVisibleCursor(::sw::VisibleCursorState const& rState, const 
SwCursorShell * pCShell);
     ~SwVisibleCursor();
 
     void Show();
@@ -60,10 +62,15 @@ public:
 
     bool IsVisible() const { return m_bIsVisible; }
     void SetDragCursor( bool bFlag = true ) { m_bIsDragCursor = bFlag; }
+    ::std::pair<SwRect, bool> SetPos();
     void SetPosAndShow(SfxViewShell const * pViewShell);
     const vcl::Cursor& GetTextCursor() const;
 
     std::optional<OString> getLOKPayload(int nType, int nViewId) const;
+
+#if defined(YRS)
+    ::std::optional<OUString> m_Author;
+#endif
 };
 
 // From here classes/methods for selections.
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
index d1d33f19e58d..27cdec5cd827 100644
--- a/sw/source/core/crsr/crsrsh.cxx
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -60,6 +60,9 @@
 #include <comphelper/flagguard.hxx>
 #include <strings.hrc>
 #include <IDocumentLayoutAccess.hxx>
+#if defined(YRS)
+#include <IDocumentState.hxx>
+#endif
 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
 #include <comphelper/lok.hxx>
 #include <comphelper/sequence.hxx>
@@ -1778,6 +1781,15 @@ void SwCursorShell::Paint(vcl::RenderContext& 
rRenderContext, const tools::Recta
         bVis = true;
         m_pVisibleCursor->Hide();
     }
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor)
+        {
+            it.second->m_pVisibleCursor->Hide();
+        }
+    }
+#endif
 
     // re-paint area
     SwViewShell::Paint(rRenderContext, rRect);
@@ -1807,6 +1819,15 @@ void SwCursorShell::Paint(vcl::RenderContext& 
rRenderContext, const tools::Recta
 
     if( m_bSVCursorVis && bVis ) // also show SV cursor again
         m_pVisibleCursor->Show();
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis)
+        {
+            it.second->m_pVisibleCursor->Show();
+        }
+    }
+#endif
 }
 
 void SwCursorShell::VisPortChgd( const SwRect & rRect )
@@ -1987,6 +2008,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
     // If the current cursor is in a table and point/mark in different boxes,
     // then the table mode is active (also if it is already active: 
m_pTableCursor)
     SwPaM* pTstCursor = getShellCursor( true );
+    // TODO yrs: table selection is possible in RO mode
     if( pTstCursor->HasMark() && !m_pBlockCursor &&
         SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) &&
           ( m_pTableCursor ||
@@ -2164,7 +2186,17 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
     }
 
     m_pVisibleCursor->Hide(); // always hide visible Cursor
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor)
+        {
+            it.second->m_pVisibleCursor->Hide();
+        }
+    }
+#endif
 
+    // for peers, leave it to the peer to move out of hidden/protected;
     // are we perhaps in a protected / hidden Section ?
     {
         SwShellCursor* pShellCursor = getShellCursor( true );
@@ -2216,7 +2248,8 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
     // loops _behind_ the last node in the selection, which always works if you
     // are in content.) To achieve this, we'll force cursor(s) to point into
     // content, if UpdateCursorPos() hasn't already done so.
-    for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer())
+    auto const MoveIntoContent = [](SwShellCursor *const pCursor) {
+    for (SwPaM& rCmp : pCursor->GetRingContainer())
     {
         // start will move forwards, end will move backwards
         bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() );
@@ -2238,12 +2271,28 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
             }
         }
     }
+    };
+    MoveIntoContent(m_pCurrentCursor);
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor)
+        {
+            MoveIntoContent(it.second->m_pCurrentCursor);
+        }
+    }
+#endif
+    SwContentFrame *pPointFrame;
 
-    SwRect aOld( m_aCharRect );
+    auto const Impl = [this, eFlags, eScrollSizeMode](::sw::VisibleCursorState 
& rState, SwContentFrame **const ppFrame, bool isThisShell)
+    {
+    SwRect aOld(rState.m_aCharRect);
     bool bFirst = true;
     SwContentFrame *pFrame;
     int nLoopCnt = 100;
-    SwShellCursor* pShellCursor = getShellCursor( true );
+    SwShellCursor* pShellCursor = isThisShell
+        ? static_cast<SwCursorShell&>(rState).getShellCursor(true)
+        : rState.m_pCurrentCursor;
 
     do {
         bool bAgainst;
@@ -2261,7 +2310,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
                 {
                     const SwStartNode* pNd = 
pShellCursor->GetPointNode().FindTableBoxStartNode();
                     if ( pNd && pNd->GetTableBox()->GetRedlineType() == 
RedlineType::Delete )
-                        return;
+                        return false;
                 }
 
                 do
@@ -2277,7 +2326,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
                 pFrame->PrepareCursor();
 
             // In protected Fly? but ignore in case of frame selection
-            if( !IsReadOnlyAvailable() && pFrame->IsProtected() &&
+            if (isThisShell && !IsReadOnlyAvailable() && pFrame->IsProtected() 
&&
                 ( !Imp()->GetDrawView() ||
                   !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() 
) &&
                 (!mxDoc->GetDocShell() ||
@@ -2300,7 +2349,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
                             GetDoc()->GetDocShell()->SetReadOnlyUI();
                             CallChgLnk();       // notify UI!
                         }
-                        return;
+                        return false;
                     }
                 }
 
@@ -2321,9 +2370,9 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
         } while( bAgainst );
 
         SwCursorMoveState aTmpState( m_eMvState );
-        aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+        aTmpState.m_bSetInReadOnly = !isThisShell || IsReadOnlyAvailable();
         aTmpState.m_bRealHeight = true;
-        aTmpState.m_bRealWidth = IsOverwriteCursor();
+        aTmpState.m_bRealWidth = rState.m_bOverwriteCursor;
         aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel();
 
         // #i27615#,#i30453#
@@ -2336,25 +2385,26 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
 
         {
             DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent 
recursive Action
-            if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), 
&aTmpState))
+            if (!pFrame->GetCharRect(rState.m_aCharRect, 
*pShellCursor->GetPoint(), &aTmpState))
             {
                 Point& rPt = pShellCursor->GetPtPos();
-                rPt = m_aCharRect.Center();
+                rPt = rState.m_aCharRect.Center();
                 pFrame->GetModelPositionForViewPoint( 
pShellCursor->GetPoint(), rPt, &aTmpState );
             }
         }
-        UISizeNotify(); // tdf#96256 update view size
+        if (isThisShell)
+            UISizeNotify(); // tdf#96256 update view size
 
         if( !pShellCursor->HasMark() )
-            m_aCursorHeight = aTmpState.m_aRealHeight;
+            rState.m_aCursorHeight = aTmpState.m_aRealHeight;
         else
         {
-            m_aCursorHeight.setX(0);
-            m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
-                              -m_aCharRect.Width() : m_aCharRect.Height());
+            rState.m_aCursorHeight.setX(0);
+            rState.m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
+                -rState.m_aCharRect.Width() : rState.m_aCharRect.Height());
         }
 
-        if( !bFirst && aOld == m_aCharRect )
+        if (!bFirst && aOld == rState.m_aCharRect)
             break;
 
         // if the layout says that we are after the 100th iteration still in
@@ -2365,14 +2415,14 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
             OSL_ENSURE( false, "endless loop? CharRect != OldCharRect ");
             break;
         }
-        aOld = m_aCharRect;
+        aOld = rState.m_aCharRect;
         bFirst = false;
 
         // update cursor Points to the new Positions
-        pShellCursor->GetPtPos().setX(m_aCharRect.Left());
-        pShellCursor->GetPtPos().setY(m_aCharRect.Top());
+        pShellCursor->GetPtPos().setX(rState.m_aCharRect.Left());
+        pShellCursor->GetPtPos().setY(rState.m_aCharRect.Top());
 
-        if( !(eFlags & SwCursorShell::UPDOWN ))   // delete old Pos. of Up/Down
+        if (isThisShell && !(eFlags & SwCursorShell::UPDOWN))   // delete old 
Pos. of Up/Down
         {
             DisableCallbackAction a(*GetLayout());
             pFrame->Calc(GetOut());
@@ -2382,7 +2432,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
         }
 
         // scroll Cursor to visible area
-        if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
+        if (isThisShell && m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
             (HasSelection() || eFlags & SwCursorShell::READONLY ||
              !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) 
)
         {
@@ -2395,8 +2445,25 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
         }
 
     } while( eFlags & SwCursorShell::SCROLLWIN );
+    *ppFrame = pFrame;
+    return true;
+    };
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor)
+        {
+            SwContentFrame *pDummy;
+            Impl(*it.second, &pDummy, false);
+        }
+    }
+#endif
+    if (!Impl(*this, &pPointFrame, true))
+    {
+        return;
+    }
 
-    assert(pFrame);
+    assert(pPointFrame);
 
     if( m_pBlockCursor )
         RefreshBlockCursor();
@@ -2420,6 +2487,15 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
                     pNxt = pNxt->GetNext();
                 }
             }
+#if defined(YRS)
+            for (auto const& it : m_PeerCursors)
+            {
+                if (it.second->m_pCurrentCursor)
+                {
+                    it.second->m_pCurrentCursor->SwSelPaintRects::Show();
+                }
+            }
+#endif
         }
     }
 
@@ -2427,7 +2503,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool 
bIdleEnd, ScrollSizeMo
 
 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
     if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
-        Imp()->InvalidateAccessibleCursorPosition( pFrame );
+        Imp()->InvalidateAccessibleCursorPosition(pPointFrame);
 #endif
 
     // switch from blinking cursor to read-only-text-selection cursor
@@ -2450,6 +2526,16 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, 
bool bIdleEnd, ScrollSizeMo
 
     if( m_bSVCursorVis )
         m_pVisibleCursor->Show(); // show again
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis)
+        {
+            it.second->m_pVisibleCursor->Show();
+        }
+    }
+    GetDoc()->getIDocumentState().YrsNotifyCursorUpdate();
+#endif
 
     if (comphelper::LibreOfficeKit::isActive())
         sendLOKCursorUpdates();
@@ -3154,6 +3240,97 @@ SwVisibleCursor* SwCursorShell::GetVisibleCursor() const
     return m_pVisibleCursor;
 }
 
+#if defined(YRS)
+void SwCursorShell::YrsAddCursor(OString const& rId, 
::std::optional<SwPosition> const& roPoint,
+        ::std::optional<SwPosition> const& roMark, OUString const& rAuthor)
+{
+    SwShellCursor * pCursor{nullptr};
+    if (roPoint)
+    {
+        pCursor = new SwShellCursor{*this, *roPoint};
+        if (roMark)
+        {
+            pCursor->SetMark();
+            *pCursor->GetMark() = *roMark;
+        }
+    }
+
+    auto const [it, inserted]{m_PeerCursors.emplace(rId, new 
VisibleCursorState{SwRect{}, Point{}, pCursor, nullptr, true, false})};
+    assert(inserted);
+    it->second->m_pVisibleCursor = new SwVisibleCursor{*it->second, this};
+    it->second->m_pVisibleCursor->m_Author = rAuthor;
+
+    // TODO check if pos is valid? just call UC? add cursor parameter so it 
doesnt check all of them?
+    UpdateCursor();
+}
+
+void SwCursorShell::YrsSetCursor(OString const& rId, 
::std::optional<SwPosition> const& roPoint,
+        ::std::optional<SwPosition> const& roMark)
+{
+    auto const it{m_PeerCursors.find(rId)};
+    assert(it != m_PeerCursors.end());
+    if (!it->second->m_pCurrentCursor && !roPoint)
+    {
+        return; // cursor moved in EE
+    }
+    if (it->second->m_pCurrentCursor && !roPoint)
+    {
+        it->second->m_pVisibleCursor->Hide();
+        it->second->m_pCurrentCursor->Hide();
+        delete it->second->m_pCurrentCursor;
+        it->second->m_pCurrentCursor = nullptr;
+        return;
+    }
+    else if (!it->second->m_pCurrentCursor && roPoint)
+    {
+        it->second->m_pCurrentCursor = new SwShellCursor{*this, *roPoint};
+    }
+    else
+    {
+        *it->second->m_pCurrentCursor->GetPoint() = *roPoint;
+    }
+    if (it->second->m_pCurrentCursor->HasMark() && !roMark)
+    {
+        it->second->m_pCurrentCursor->DeleteMark();
+    }
+    else if (roMark)
+    {
+        if (!it->second->m_pCurrentCursor->HasMark())
+        {
+            it->second->m_pCurrentCursor->SetMark();
+        }
+        *it->second->m_pCurrentCursor->GetMark() = *roMark;
+    }
+
+    // TODO check if pos is valid? just call UC? add cursor parameter so it 
doesnt check all of them?
+    UpdateCursor();
+}
+
+void SwCursorShell::YrsDelCursor(OString const& rId)
+{
+    auto const it{m_PeerCursors.find(rId)};
+    assert(it != m_PeerCursors.end());
+    if (it->second->m_pCurrentCursor)
+    {
+        it->second->m_pCurrentCursor->Hide();
+    }
+    m_PeerCursors.erase(it);
+}
+
+SwVisibleCursor * SwCursorShell::FindVisibleCursorForPeer(SwSelPaintRects 
const& rCursor) const
+{
+    for (auto const& it : m_PeerCursors)
+    {
+        if (it.second->m_pCurrentCursor == &rCursor)
+        {
+            return it.second->m_pVisibleCursor;
+        }
+    }
+    return nullptr;
+}
+
+#endif
+
 bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const
 {
     Point aPt( rPt );
@@ -3389,7 +3566,7 @@ SwCursorShell::SwCursorShell( SwCursorShell& rShell, 
vcl::Window *pInitWin )
     m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
     m_bSVCursorVis = true;
     m_bSetCursorInReadOnly = true;
-    m_pVisibleCursor = new SwVisibleCursor( this );
+    m_pVisibleCursor = new SwVisibleCursor(*this, this);
     m_bMacroExecAllowed = rShell.IsMacroExecAllowed();
 
     m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
@@ -3439,7 +3616,7 @@ SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window 
*pInitWin,
     m_bSVCursorVis = true;
     m_bSetCursorInReadOnly = true;
 
-    m_pVisibleCursor = new SwVisibleCursor( this );
+    m_pVisibleCursor = new SwVisibleCursor(*this, this);
     m_bMacroExecAllowed = true;
 
     m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
@@ -3456,6 +3633,14 @@ SwCursorShell::~SwCursorShell()
     else
         ClearTableBoxContent();
 
+#if defined(YRS)
+    for (auto const& it : m_PeerCursors)
+    {
+        delete it.second->m_pVisibleCursor;
+        delete it.second->m_pCurrentCursor;
+    }
+#endif
+
     delete m_pVisibleCursor;
     delete m_pBlockCursor;
     delete m_pTableCursor;
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
index e492544a4805..2e640750dcb3 100644
--- a/sw/source/core/crsr/viscrs.cxx
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -36,6 +36,7 @@
 #include <txtfld.hxx>
 #include <scriptinfo.hxx>
 #include <view.hxx>
+#include <swmodule.hxx>
 #include <IDocumentLayoutAccess.hxx>
 
 #include <svx/sdr/overlay/overlaymanager.hxx>
@@ -75,11 +76,16 @@ tools::Long SwSelPaintRects::s_nPixPtY = 0;
 MapMode* SwSelPaintRects::s_pMapMode = nullptr;
 
 // Starting from here: classes / methods for the non-text-cursor
-SwVisibleCursor::SwVisibleCursor( const SwCursorShell * pCShell )
-    : m_pCursorShell( pCShell )
+SwVisibleCursor::SwVisibleCursor(::sw::VisibleCursorState const& rState,
+        const SwCursorShell *const pCShell)
+    : m_rState(rState)
+    , m_pCursorShell(pCShell)
     , m_nPageLastTime(0)
 {
-    pCShell->GetWin()->SetCursor( &m_aTextCursor );
+    if (&rState == pCShell)
+    {
+        pCShell->GetWin()->SetCursor( &m_aTextCursor );
+    }
     m_bIsVisible = m_aTextCursor.IsVisible();
     m_bIsDragCursor = false;
     m_aTextCursor.SetWidth( 0 );
@@ -90,7 +96,10 @@ SwVisibleCursor::~SwVisibleCursor()
     if( m_bIsVisible && m_aTextCursor.IsVisible() )
         m_aTextCursor.Hide();
 
-    m_pCursorShell->GetWin()->SetCursor( nullptr );
+    if (m_pCursorShell->GetWin()->GetCursor() == &m_aTextCursor)
+    {
+        m_pCursorShell->GetWin()->SetCursor( nullptr );
+    }
 }
 
 void SwVisibleCursor::Show()
@@ -100,7 +109,7 @@ void SwVisibleCursor::Show()
         m_bIsVisible = true;
 
         // display at all?
-        if( m_pCursorShell->VisArea().Overlaps( m_pCursorShell->m_aCharRect ) 
|| comphelper::LibreOfficeKit::isActive() )
+        if (m_pCursorShell->VisArea().Overlaps(m_rState.m_aCharRect) || 
comphelper::LibreOfficeKit::isActive())
             SetPosAndShow(nullptr);
     }
 }
@@ -133,33 +142,33 @@ OString buildHyperlinkJSON(const OUString& sText, const 
OUString& sLink)
 
 }
 
-void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell)
+::std::pair<SwRect, bool> SwVisibleCursor::SetPos()
 {
     SwRect aRect;
-    tools::Long nTmpY = m_pCursorShell->m_aCursorHeight.getY();
+    tools::Long nTmpY = m_rState.m_aCursorHeight.getY();
     if( 0 > nTmpY )
     {
         nTmpY = -nTmpY;
         m_aTextCursor.SetOrientation( 900_deg10 );
-        aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
-           Size( m_pCursorShell->m_aCharRect.Height(), nTmpY ) );
-        aRect.Pos().setX(aRect.Pos().getX() + 
m_pCursorShell->m_aCursorHeight.getX());
-        if( m_pCursorShell->IsOverwriteCursor() )
+        aRect = SwRect( m_rState.m_aCharRect.Pos(),
+           Size( m_rState.m_aCharRect.Height(), nTmpY ) );
+        aRect.Pos().setX(aRect.Pos().getX() + m_rState.m_aCursorHeight.getX());
+        if (m_rState.IsOverwriteCursor())
             aRect.Pos().setY(aRect.Pos().getY() + aRect.Width());
     }
     else
     {
         m_aTextCursor.SetOrientation();
-        aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
-           Size( m_pCursorShell->m_aCharRect.Width(), nTmpY ) );
-        aRect.Pos().setY(aRect.Pos().getY() + 
m_pCursorShell->m_aCursorHeight.getX());
+        aRect = SwRect( m_rState.m_aCharRect.Pos(),
+           Size( m_rState.m_aCharRect.Width(), nTmpY ) );
+        aRect.Pos().setY(aRect.Pos().getY() + m_rState.m_aCursorHeight.getX());
     }
 
     // check if cursor should show the current cursor bidi level
     m_aTextCursor.SetDirection();
-    const SwCursor* pTmpCursor = m_pCursorShell->GetCursor_();
+    const SwCursor* pTmpCursor = m_rState.m_pCurrentCursor;
 
-    if ( pTmpCursor && !m_pCursorShell->IsOverwriteCursor() )
+    if ( pTmpCursor && !m_rState.IsOverwriteCursor() )
     {
         SwNode& rNode = pTmpCursor->GetPoint()->GetNode();
         if( rNode.IsTextNode() )
@@ -201,7 +210,7 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * 
pViewShell)
         if (!comphelper::LibreOfficeKit::isActive())
             ::SwAlignRect( aRect, static_cast<SwViewShell const 
*>(m_pCursorShell), m_pCursorShell->GetOut() );
     }
-    if( !m_pCursorShell->IsOverwriteCursor() || m_bIsDragCursor ||
+    if( !m_rState.IsOverwriteCursor() || m_bIsDragCursor ||
         m_pCursorShell->IsSelection() )
         aRect.Width( 0 );
 
@@ -209,6 +218,12 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * 
pViewShell)
 
     m_aTextCursor.SetSize( aRect.SSize() );
     m_aTextCursor.SetPos( aRect.Pos() );
+    return { aRect, bIsCursorPosChanged };
+}
+
+void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell)
+{
+    auto const [aRect, bIsCursorPosChanged] {SetPos()};
 
     if (comphelper::LibreOfficeKit::isActive())
     {
@@ -346,7 +361,7 @@ const vcl::Cursor& SwVisibleCursor::GetTextCursor() const
     return m_aTextCursor;
 }
 
-SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh )
+SwSelPaintRects::SwSelPaintRects(const SwCursorShell& rCSh)
     : m_pCursorShell( &rCSh )
 #if HAVE_FEATURE_DESKTOP
     , m_bShowTextInputFieldOverlay(true)
@@ -456,8 +471,16 @@ void SwSelPaintRects::Show(std::vector<OString>* 
pSelectionRectangles)
 
         if (xTargetOverlay.is())
         {
+            ::std::optional<Color> oColor;
+#if defined(YRS)
+            if (SwVisibleCursor *const 
pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)})
+            {
+                ::std::size_t const 
authorId{SwModule::get()->InsertRedlineAuthor(*pVisibleCursor->m_Author)};
+                oColor.emplace(SwPostItMgr::GetColorAnchor(authorId));
+            }
+#endif
             // get the system's highlight color
-            const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
+            const Color aHighlight(oColor ? *oColor : 
SvtOptionsDrawinglayer::getHilightColor());
 
             // create correct selection
             m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection(
@@ -943,6 +966,25 @@ void SwShellCursor::FillRects()
     {
         GetShell()->GetLayout()->CalcFrameRects(*this, *this);
     }
+#if defined(YRS)
+    if (!HasMark())
+    {
+        if (SwVisibleCursor *const 
pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)})
+        {
+            // use OutDev.GetSettings().GetStyleSettings().GetCursorSize() as 
width?
+            auto [cursorRect, _] {pVisibleCursor->SetPos()};
+            if (cursorRect.IsEmpty())
+            {
+                cursorRect.Width(20);
+            }
+            SAL_DEBUG("YRS FillRects extra rect " << cursorRect);
+            emplace_back(cursorRect);
+            SwRect const temp{Point{cursorRect.Left() - cursorRect.Height()/2,
+                cursorRect.Bottom()}, Size{cursorRect.Height() + 20, 20}};
+            emplace_back(temp);
+        }
+    }
+#endif
 }
 
 void SwShellCursor::Show(SfxViewShell const * pViewShell)
diff --git a/sw/source/core/doc/DocumentStateManager.cxx 
b/sw/source/core/doc/DocumentStateManager.cxx
index 011c3c1e03e6..91f39d7f7634 100644
--- a/sw/source/core/doc/DocumentStateManager.cxx
+++ b/sw/source/core/doc/DocumentStateManager.cxx
@@ -29,6 +29,7 @@
 #include <view.hxx>
 #include <docufld.hxx>
 #include <PostItMgr.hxx>
+#include <swmodule.hxx>
 #include <IDocumentFieldsAccess.hxx>
 #include <txtannotationfld.hxx>
 #include <ndtxt.hxx>
@@ -120,7 +121,9 @@ YrsTransactionSupplier::YrsTransactionSupplier()
 
 YrsTransactionSupplier::~YrsTransactionSupplier()
 {
-    assert(m_pCurrentWriteTransaction == nullptr);
+    // with cursors there will always be a pending transaction, and there is 
no api to cancel it, so just ignore it...
+    //assert(m_pCurrentWriteTransaction == nullptr);
+    (void) m_pCurrentWriteTransaction;
 }
 
 YTransaction * YrsTransactionSupplier::GetReadTransaction()
@@ -623,13 +626,217 @@ extern "C" void observe_comments(void *const pState, 
uint32_t count, YEvent cons
     }
 }
 
-extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent 
const*const events)
+struct ObserveCursorState : public ObserveState
 {
-#if 1
-    (void) pState;
-    (void) count;
-    (void) events;
+    struct Update {
+        OString const peerId;
+        ::std::optional<OUString> const oAuthor;
+        ::std::pair<int64_t, int64_t> const point;
+        ::std::optional<::std::pair<int64_t, int64_t>> const oMark;
+    };
+    ::std::vector<Update> CursorUpdates;
+};
+
+void YrsCursorUpdates(ObserveCursorState & rState)
+{
+    for (auto const& it : rState.CursorUpdates)
+    {
+#if defined(LOPLUGIN_BLOCK_BLOCK)
 #endif
+        {
+            ::std::optional<SwPosition> oMark;
+            if (it.oMark)
+            {
+                yvalidate(SwNodeOffset{it.oMark->first} < 
rState.rDoc.GetNodes().Count());
+                SwNode & 
rNode{*rState.rDoc.GetNodes()[SwNodeOffset{it.oMark->first}]};
+                yvalidate(rNode.IsTextNode());
+                yvalidate(it.oMark->second <= 
o3tl::make_unsigned(rNode.GetTextNode()->Len()));
+                oMark.emplace(*rNode.GetTextNode(), 
static_cast<sal_Int32>(it.oMark->second));
+            }
+
+            yvalidate(SwNodeOffset{it.point.first} < 
rState.rDoc.GetNodes().Count());
+            SwNode & 
rNode{*rState.rDoc.GetNodes()[SwNodeOffset{it.point.first}]};
+            yvalidate(rNode.IsTextNode());
+            yvalidate(it.point.second <= 
o3tl::make_unsigned(rNode.GetTextNode()->Len()));
+            SwPosition const point{*rNode.GetTextNode(), 
static_cast<sal_Int32>(it.point.second)};
+
+            for (SwViewShell & rShell : 
rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer())
+            {
+                if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)})
+                {
+                    if (it.oAuthor)
+                    {
+                        pShell->YrsAddCursor(it.peerId, {point}, oMark, 
*it.oAuthor);
+                    }
+                    else
+                    {
+                        pShell->YrsSetCursor(it.peerId, {point}, oMark);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void YrsReadCursor(ObserveCursorState & rState, OString const& rPeerId,
+    YOutput const& rCursor, ::std::optional<OUString> const oAuthor)
+{
+    switch (rCursor.tag)
+    {
+        case Y_ARRAY:
+        {
+            Branch const*const pArray{rCursor.value.y_type};
+            auto const len{yarray_len(pArray)};
+            if (len == 3 || len == 5)
+            {
+                // TODO cursor in EE
+            }
+            else if (len == 2 || len == 4)
+            {
+                // the only reason this stuff has a hope of working with
+                // integers is that the SwNodes is read-only
+                ::std::optional<::std::pair<int64_t, int64_t>> oMark;
+                if (len == 4)
+                {
+                    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pNode{yarray_get(pArray, rState.pTxn, 2)};
+                    yvalidate(pNode->tag == Y_JSON_INT);
+                    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pContent{yarray_get(pArray, rState.pTxn, 3)};
+                    yvalidate(pContent->tag == Y_JSON_INT);
+                    oMark.emplace(pNode->value.integer, 
pContent->value.integer);
+                }
+
+                ::std::unique_ptr<YOutput, YOutputDeleter> const 
pNode{yarray_get(pArray, rState.pTxn, 0)};
+                yvalidate(pNode->tag == Y_JSON_INT);
+                ::std::unique_ptr<YOutput, YOutputDeleter> const 
pContent{yarray_get(pArray, rState.pTxn, 1)};
+                yvalidate(pContent->tag == Y_JSON_INT);
+                ::std::pair<int64_t, int64_t> const pos{pNode->value.integer, 
pContent->value.integer};
+                rState.CursorUpdates.emplace_back(rPeerId, oAuthor, pos, 
oMark);
+            }
+            else
+                yvalidate(false);
+            break;
+        }
+        case Y_JSON_NULL:
+            for (SwViewShell & rShell : 
rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer())
+            {
+                if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)})
+                {
+                    if (oAuthor)
+                    {
+                        pShell->YrsAddCursor(rPeerId, {}, {}, *oAuthor);
+                    }
+                    else
+                    {
+                        pShell->YrsSetCursor(rPeerId, {}, {});
+                    }
+                }
+            }
+            break;
+        default:
+            yvalidate(false);
+    }
+}
+
+extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent 
const*const events)
+{
+    SAL_DEBUG("YRS observe_cursors");
+    // note: it (very rarely) happens that observe_cursors will be called
+    // when a cursor is moved into a comment that is newly inserted, but
+    // observe_comments hasn't been called to actually insert the comment yet
+    // => need to buffer all the cursor updates and replay them at the end...
+    ObserveCursorState & rState{*static_cast<ObserveCursorState*>(pState)};
+
+    for (decltype(count) i = 0; i < count; ++i)
+    {
+        switch (events[i].tag)
+        {
+            case Y_MAP:
+            {
+                // new peer?
+                YMapEvent const*const pEvent{&events[i].content.map};
+                uint32_t lenP{0};
+                /*YPathSegment *const pPath{*/ymap_event_path(pEvent, &lenP);
+                yvalidate(lenP == 0);
+                uint32_t lenK{0};
+                YEventKeyChange *const pChange{ymap_event_keys(pEvent, &lenK)};
+                for (decltype(lenK) j = 0; j < lenK; ++j)
+                {
+                    OString const peerId{pChange[j].key};
+                    yvalidate(peerId != 
OString::number(ydoc_id(rState.rYrsSupplier.GetYDoc()))); // should never be 
updated by peers?
+                    switch (pChange[j].tag)
+                    {
+                        case Y_EVENT_KEY_CHANGE_UPDATE:
+                            yvalidate(false);
+                        case Y_EVENT_KEY_CHANGE_ADD:
+                        {
+                            switch (pChange[j].new_value->tag)
+                            {
+                                case Y_ARRAY:
+                                {
+                                    Branch const*const 
pArray{pChange[j].new_value->value.y_type};
+                                    auto const len{yarray_len(pArray)};
+                                    yvalidate(len == 2);
+                                    ::std::unique_ptr<YOutput, YOutputDeleter> 
const pAuthor{
+                                        yarray_get(pArray, rState.pTxn, 0)};
+                                    yvalidate(pAuthor->tag == Y_JSON_STR && 
pAuthor->len < SAL_MAX_INT32);
+                                    OUString const author{pAuthor->value.str,
+                                        static_cast<sal_Int32>(pAuthor->len), 
RTL_TEXTENCODING_UTF8};
+                                    ::std::unique_ptr<YOutput, YOutputDeleter> 
const pCursor{
+                                        yarray_get(pArray, rState.pTxn, 1)};
+                                    YrsReadCursor(rState, peerId, *pCursor, 
{author});
+                                    break;
+                                }
+                                default:
+                                    yvalidate(false);
+                            }
+                            break;
+                        }
+                        case Y_EVENT_KEY_CHANGE_DELETE:
+                        {
+                            for (SwViewShell & rShell : 
rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer())
+                            {
+                                if (auto const 
pShell{dynamic_cast<SwCursorShell *>(&rShell)})
+                                {
+                                    pShell->YrsDelCursor(peerId);
+                                }
+                            }
+                            assert(false); // TODO cannot test this currently?
+                            break;
+                        }
+                        default:
+                            assert(false);
+                    }
+                }
+                break;
+            }
+            case Y_ARRAY:
+            {
+                YArrayEvent const*const pEvent{&events[i].content.array};
+                // position changed?
+                uint32_t lenP{0};
+                YPathSegment *const pPath{yarray_event_path(pEvent, &lenP)};
+                yvalidate(lenP == 1);
+                yvalidate(pPath[0].tag == Y_EVENT_PATH_KEY);
+                OString const peerId{pPath[0].value.key};
+                ypath_destroy(pPath, lenP);
+
+                uint32_t lenC{0};
+                YEventChange *const pChange{yarray_event_delta(pEvent, &lenC)};
+                // position update looks like this
+                yvalidate(lenC == 3);
+                yvalidate(pChange[0].tag == Y_EVENT_CHANGE_RETAIN);
+                yvalidate(pChange[0].len == 1);
+                yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE);
+                yvalidate(pChange[1].len == 1);
+                yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD);
+                yvalidate(pChange[2].len == 1);
+                YrsReadCursor(rState, peerId, pChange[2].values[0], {});
+                break;
+            }
+            default:
+                assert(false);
+        }
+    }
 }
 
 void writeLength(sal_Int8 *& rpBuf, sal_Int32 const len)
@@ -726,9 +933,11 @@ IMPL_LINK(YrsThread, HandleMessage, void*, pVoid, void)
             // YText - need to be notified on new comments being created...
             ObserveState state{*m_pDSM->m_pYrsSupplier, m_pDSM->m_rDoc, pTxn};
             YSubscription *const pSubComments = 
yobserve_deep(m_pDSM->m_pYrsSupplier->GetCommentMap(), &state, 
observe_comments);
+            ObserveCursorState cursorState{*m_pDSM->m_pYrsSupplier, 
m_pDSM->m_rDoc, pTxn, {}};
             // not sure if yweak_observe would work for (weakref) cursors
-            YSubscription *const pSubCursors = 
yobserve_deep(m_pDSM->m_pYrsSupplier->GetCursorMap(), &state, observe_cursors);
+            YSubscription *const pSubCursors = 
yobserve_deep(m_pDSM->m_pYrsSupplier->GetCursorMap(), &cursorState, 
observe_cursors);
             m_pDSM->m_pYrsSupplier->CommitTransaction(true);
+            YrsCursorUpdates(cursorState);
             
m_pDSM->m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Edit);
             yunobserve(pSubComments);
             yunobserve(pSubCursors);
@@ -956,6 +1165,127 @@ void DocumentStateManager::YrsRemoveComment(SwPosition 
const& rPos, OString cons
     // either update the cursors here, or wait for round-trip?
 }
 
+void DocumentStateManager::YrsNotifyCursorUpdate()
+{
+    SwWrtShell *const 
pShell{dynamic_cast<SwWrtShell*>(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())};
+    if (!m_pYrsSupplier || !pShell->GetView().GetPostItMgr())
+    {
+        return;
+    }
+    SAL_DEBUG("YRS NotifyCursorUpdate");
+    YTransaction *const pTxn{m_pYrsSupplier->GetWriteTransaction()};
+    YDoc *const pYDoc{m_pYrsSupplier->GetYDoc()};
+    auto const id{ydoc_id(pYDoc)};
+    ::std::unique_ptr<YOutput, YOutputDeleter> 
pEntry{ymap_get(m_pYrsSupplier->m_pCursors, pTxn, 
OString::number(id).getStr())};
+    if (pEntry == nullptr)
+    {
+        OString const 
author{OUStringToOString(SwModule::get()->GetRedlineAuthor(SwModule::get()->GetRedlineAuthor()),
 RTL_TEXTENCODING_UTF8)};
+        ::std::vector<YInput> elements;
+        elements.push_back(yinput_string(author.getStr()));
+        elements.push_back(yinput_null());
+        YInput const input{yinput_yarray(elements.data(), 2)};
+        ymap_insert(m_pYrsSupplier->m_pCursors, pTxn, 
OString::number(id).getStr(), &input);
+        pEntry.reset(ymap_get(m_pYrsSupplier->m_pCursors, pTxn, 
OString::number(id).getStr()));
+    }
+    assert(pEntry);
+    yvalidate(pEntry->tag == Y_ARRAY);
+    yvalidate(yarray_len(pEntry->value.y_type) == 2);
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pCurrent{yarray_get(pEntry->value.y_type, pTxn, 1)};
+    if (sw::annotation::SwAnnotationWin *const pWin{
+            pShell->GetView().GetPostItMgr()->GetActiveSidebarWin()})
+    {
+        // TODO StickyIndex cannot be inserted into YDoc ?
+        ESelection const sel{pWin->GetOutlinerView()->GetSelection()};
+        ::std::vector<YInput> positions;
+        // the ID of the comment
+        
positions.push_back(yinput_string(pWin->GetOutlinerView()->GetEditView().GetYrsCommentId().getStr()));
+        positions.push_back(yinput_long(sel.start.nPara));
+        positions.push_back(yinput_long(sel.start.nIndex));
+        if (pWin->GetOutlinerView()->HasSelection())
+        {
+            positions.push_back(yinput_long(sel.end.nPara));
+            positions.push_back(yinput_long(sel.end.nIndex));
+            if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY
+                || yarray_len(pCurrent->value.y_type) != 5
+                || strcmp(yarray_get(pCurrent->value.y_type, pTxn, 
0)->value.str, positions[0].value.str) != 0
+                || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer 
!= positions[1].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer 
!= positions[2].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 3)->value.integer 
!= positions[3].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 4)->value.integer 
!= positions[4].value.integer)
+            {
+                YInput const input{yinput_yarray(positions.data(), 5)};
+                yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1);
+                yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1);
+                YrsCommitModified();
+            }
+        }
+        else
+        {
+            if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY
+                || yarray_len(pCurrent->value.y_type) != 3
+                || strcmp(yarray_get(pCurrent->value.y_type, pTxn, 
0)->value.str, positions[0].value.str) != 0
+                || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer 
!= positions[1].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer 
!= positions[2].value.integer)
+            {
+                YInput const input{yinput_yarray(positions.data(), 3)};
+                yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1);
+                yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1);
+                YrsCommitModified();
+            }
+        }
+    }
+    else if (!pShell->IsStdMode()
+           || pShell->GetSelectedObjCount() != 0
+           || pShell->IsTableMode()
+           || pShell->IsBlockMode())
+    {
+        if (pCurrent == nullptr || pCurrent->tag != Y_JSON_NULL)
+        {
+            YInput const input{yinput_null()};
+            yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1);
+            yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1);
+            YrsCommitModified();
+        }
+    }
+    else
+    {
+        SwPaM const*const pCursor{pShell->GetCursor()};
+        ::std::vector<YInput> positions;
+        
positions.push_back(yinput_long(pCursor->GetPoint()->GetNodeIndex().get()));
+        
positions.push_back(yinput_long(pCursor->GetPoint()->GetContentIndex()));
+        if (pCursor->HasMark())
+        {
+            
positions.push_back(yinput_long(pCursor->GetMark()->GetNodeIndex().get()));
+            
positions.push_back(yinput_long(pCursor->GetMark()->GetContentIndex()));
+            if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY
+                || yarray_len(pCurrent->value.y_type) != 4
+                || yarray_get(pCurrent->value.y_type, pTxn, 0)->value.integer 
!= positions[0].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer 
!= positions[1].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer 
!= positions[2].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 3)->value.integer 
!= positions[3].value.integer)
+            {
+                YInput const input{yinput_yarray(positions.data(), 4)};
+                yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1);
+                yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1);
+                YrsCommitModified();
+            }
+        }
+        else
+        {
+            if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY
+                || yarray_len(pCurrent->value.y_type) != 2
+                || yarray_get(pCurrent->value.y_type, pTxn, 0)->value.integer 
!= positions[0].value.integer
+                || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer 
!= positions[1].value.integer)
+            {
+                YInput const input{yinput_yarray(positions.data(), 2)};
+                yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1);
+                yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1);
+                YrsCommitModified();
+            }
+        }
+    }
+}
+
 void DocumentStateManager::YrsInitAcceptor()
 {
     if (!getenv("YRSACCEPT") || m_pYrsReader.is())
@@ -1049,7 +1379,7 @@ void DocumentStateManager::YrsInitAcceptor()
         }
         // initiate sync of comments to other side
     //AddComment would have done this    m_pYrsSupplier->GetWriteTransaction();
-        YrsCommitModified();
+        YrsNotifyCursorUpdate();
     }
     catch (uno::Exception const&) // exception here will cause UAF from SwView 
later
     {
diff --git a/sw/source/core/doc/doccorr.cxx b/sw/source/core/doc/doccorr.cxx
index 61cad6e1eca4..177b8becb957 100644
--- a/sw/source/core/doc/doccorr.cxx
+++ b/sw/source/core/doc/doccorr.cxx
@@ -121,6 +121,19 @@ void PaMCorrAbs( const SwPaM& rRange,
 
             if( pCursorShell->IsTableMode() )
                 lcl_PaMCorrAbs( const_cast<SwPaM 
&>(*pCursorShell->GetTableCrs()), aStart, aEnd, aNewPos );
+
+#if defined(YRS)
+            for (auto const& it : pCursorShell->m_PeerCursors)
+            {
+                if (it.second->m_pCurrentCursor)
+                {
+                    for (SwPaM & rPaM : 
it.second->m_pCurrentCursor->GetRingContainer())
+                    {
+                        lcl_PaMCorrAbs(rPaM, aStart, aEnd, aNewPos);
+                    }
+                }
+            }
+#endif
         }
     }
 
@@ -279,6 +292,16 @@ void PaMCorrRel( const SwNode &rOldNode,
 
             if( pCursorShell->IsTableMode() )
                 lcl_PaMCorrRel1( pCursorShell->GetTableCrs(), pOldNode, 
aNewPos, nCntIdx );
+
+#if defined(YRS)
+            for (auto const& it : pCursorShell->m_PeerCursors)
+            {
+                for (SwPaM & rPaM : 
it.second->m_pCurrentCursor->GetRingContainer())
+                {
+                    lcl_PaMCorrRel1(&rPaM, pOldNode, aNewPos, nCntIdx);
+                }
+            }
+#endif
         }
     }
 
diff --git a/sw/source/core/inc/DocumentStateManager.hxx 
b/sw/source/core/inc/DocumentStateManager.hxx
index 0457e7920f70..b26f3bc9d58e 100644
--- a/sw/source/core/inc/DocumentStateManager.hxx
+++ b/sw/source/core/inc/DocumentStateManager.hxx
@@ -87,6 +87,7 @@ public:
             SwPostItField const& rField, bool isInsert) override;
     void YrsRemoveCommentImpl(OString const& rCommentId) override;
     void YrsRemoveComment(SwPosition const& rPos, OString const& rCommentId) 
override;
+    void YrsNotifyCursorUpdate() override;
 #endif
 };
 

Reply via email to