blob: 6e89fe1b3e8452c4fca450786e326cd7e50e01f8 [file] [log] [blame]
Michael Stahl8f803412024-10-25 15:02:34 +02001
2## Experimental Writer comments editing collaboration with yrs
3
4### How to build
5
6First, build yrs C FFI bindings:
7
8```
9 git clone https://github.com/y-crdt/y-crdt.git
10 cd y-crdt
Michael Stahl88055a22025-06-24 15:32:52 +020011 git checkout v0.23.5
Michael Stahl8f803412024-10-25 15:02:34 +020012 cargo build -p yffi
13```
14
15Then, put the yrs build directory in autogen.input:
16
17`--with-yrs=/path/to/y-crdt`
18
Michael Stahl094480a2025-05-26 11:36:04 +020019All the related code should be behind macros in `config_host/config_collab.h`
20
Michael Stahl8f803412024-10-25 15:02:34 +020021### How to run
22
23To prevent crashes at runtime, set the environment variable
24EDIT_COMMENT_IN_READONLY_MODE=1 and open documents in read-only mode: only
25inserting/deleting comments, and editing inside comments will be enabled.
26
27Currently, communication happens over a hard-coded pipe:
28
29* start an soffice with YRSACCEPT=1 load a Writer document and it will listen
30 and block until connect
31 (you can also create a new Writer document but that will be boring if all
32 you can do is insert comments into empty doc)
33
Michael Stahl094480a2025-05-26 11:36:04 +020034* start another soffice with YRSCONNECT=1 with a different user profile,
35 create new Writer document, and it will connect and load the document from
36 the other side
Michael Stahl8f803412024-10-25 15:02:34 +020037
38All sorts of paragraph and character formattings should work inside comments.
39
Michael Stahl094480a2025-05-26 11:36:04 +020040Peer cursors should be displayed both in the sw document body and inside
41comments.
42
Michael Stahl8f803412024-10-25 15:02:34 +020043Inserting hyperlinks also works, although sadly i wasn't able to figure out
44how to enable the menu items in read-only mode, so it only works in editable
45mode.
46
Michael Stahl8f803412024-10-25 15:02:34 +020047Switching to editable mode is also possible, but only comment-related editing
48is synced via yrs, so if other editing operations change the positions of
49comments, a crash will be inevitable.
50
Michael Stahl094480a2025-05-26 11:36:04 +020051### Implementation
52
53Most of it is in 2 classes: EditDoc and sw::DocumentStateManager (for now);
54the latter gets a new member YrsTransactionSupplier.
55
56DocumentStateManager starts a thread to communicate, and this sends new
57messages to the main thread via PostUserEvent().
58
59The EditDoc models of the comments are duplicated in a yrs YDocument model
60and this is then synced remotely by yrs.
61
62The structure of the yrs model is:
63
64* YMap of comments (key: CommentId created from peer id + counter)
65 - YArray
66 - anchor pos: 2 or 4 ints [manually updated when editing sw]
67 - YMap of comment properties
68 - YText containing mapped EditDoc state
69* YMap of cursors (key: peer id)
70 - either sw cursor: 2 or 4 ints [manually updated when editing sw]
71 or EditDoc position: CommentId + 2 or 4 ints, or WeakRef
72 or Y_JSON_NULL (for sw non-text selections, effectively ignored)
73
74Some confusing object relationships:
75
76SwAnnotationWin -> Outliner -> OutlinerEditEng -> EditEngine -> ImpEditEngine -> EditDoc
77 -> OutlinerView -> EditView -> ImpEditView -> EditEngine
78
79 -> SidebarTextControl
80
81### Undo
82
83There was no Undo for edits inside comments anyway, only when losing the
84focus a SwUndoFieldFromDoc is created.
85
86There are 2 approaches how Undo could work: either let all the SwUndo
87actions modify the yrs model, or use the yrs yundo_manager and ensure that
88for every top-level SwUndo there is exactly one item in the yundo_manager's
89stack, so that Undo/Redo will have the same effect in the sw model and
90the yrs model.
91
92Let's try if the second approach can be made to work.
93
94The yundo_manager by default creates stack items based on a timer, so we
95configure that to a 2^31 timeout and invoke yundo_manager_stop() to create
96all the items manually.
97
98yundo_manager_undo()/redo() etc internally create a YTransaction and commit
99it, which will of course fail if one already exists!
100
101The yundo_manager creates a new item also for things like cursor movements
102(because we put the cursors in the same YDocument as the content); there is
103a way to filter the changes by Branch, but that filtering happens when the
104undo/redo is invoked, not when the stack item is created - so the
105"temporary" stack item is always extended with yrs changes until a "real"
106change that has a corresponding SwUndo happens and there is a corresponding
107yundo_manager_stop() then.
108
109There are still some corner cases where the 2 undo stacks aren't synced so
110there are various workarounds like DummyUndo action or m_nTempUndoOffset
111counter for these.
112
113Also the SwUndoFieldFromDoc batch action is problematic: it is created when
114the comment loses focus, but all editing operations in the comment are
115inserted in the yrs model immediately as they happen - so if changes are
116received from peers, the creation of the SwUndoFieldFromDoc must be forced
117to happen before the peer changes are applied in the sw document model, to
118keep the actions in order.
119
120The comment id is used as a key in yrs ymap so it should be the same when
121the Undo or Redo inserts a comment again, hence it's added to SwPostItField.
122
123For edits that are received from peers, what happens is that all the
124received changes must be grouped into one SwUndo (list) action because there
125is going to be one yrs undo item; and invoking Undo will just send the
126changes to peers and they will create a new undo stack item as the local
127instance goes back in the undo stack, and it's not possible to do it
128differently because the undo stack items may contain different changes on
129each peer.
130