mirror of
https://github.com/LibreOffice/core.git
synced 2025-08-16 14:47:01 +00:00

Change-Id: I957cdaa770f34c48ca1283f6d0f3b89d66064f8c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186902 Reviewed-by: Michael Stahl <michael.stahl@allotropia.de> Tested-by: Jenkins
131 lines
5.0 KiB
Plaintext
131 lines
5.0 KiB
Plaintext
|
|
## Experimental Writer comments editing collaboration with yrs
|
|
|
|
### How to build
|
|
|
|
First, build yrs C FFI bindings:
|
|
|
|
```
|
|
git clone https://github.com/y-crdt/y-crdt.git
|
|
cd y-crdt
|
|
git checkout v0.23.5
|
|
cargo build -p yffi
|
|
```
|
|
|
|
Then, put the yrs build directory in autogen.input:
|
|
|
|
`--with-yrs=/path/to/y-crdt`
|
|
|
|
All the related code should be behind macros in `config_host/config_collab.h`
|
|
|
|
### How to run
|
|
|
|
To prevent crashes at runtime, set the environment variable
|
|
EDIT_COMMENT_IN_READONLY_MODE=1 and open documents in read-only mode: only
|
|
inserting/deleting comments, and editing inside comments will be enabled.
|
|
|
|
Currently, communication happens over a hard-coded pipe:
|
|
|
|
* start an soffice with YRSACCEPT=1 load a Writer document and it will listen
|
|
and block until connect
|
|
(you can also create a new Writer document but that will be boring if all
|
|
you can do is insert comments into empty doc)
|
|
|
|
* start another soffice with YRSCONNECT=1 with a different user profile,
|
|
create new Writer document, and it will connect and load the document from
|
|
the other side
|
|
|
|
All sorts of paragraph and character formattings should work inside comments.
|
|
|
|
Peer cursors should be displayed both in the sw document body and inside
|
|
comments.
|
|
|
|
Inserting hyperlinks also works, although sadly i wasn't able to figure out
|
|
how to enable the menu items in read-only mode, so it only works in editable
|
|
mode.
|
|
|
|
Switching to editable mode is also possible, but only comment-related editing
|
|
is synced via yrs, so if other editing operations change the positions of
|
|
comments, a crash will be inevitable.
|
|
|
|
### Implementation
|
|
|
|
Most of it is in 2 classes: EditDoc and sw::DocumentStateManager (for now);
|
|
the latter gets a new member YrsTransactionSupplier.
|
|
|
|
DocumentStateManager starts a thread to communicate, and this sends new
|
|
messages to the main thread via PostUserEvent().
|
|
|
|
The EditDoc models of the comments are duplicated in a yrs YDocument model
|
|
and this is then synced remotely by yrs.
|
|
|
|
The structure of the yrs model is:
|
|
|
|
* YMap of comments (key: CommentId created from peer id + counter)
|
|
- YArray
|
|
- anchor pos: 2 or 4 ints [manually updated when editing sw]
|
|
- YMap of comment properties
|
|
- YText containing mapped EditDoc state
|
|
* YMap of cursors (key: peer id)
|
|
- either sw cursor: 2 or 4 ints [manually updated when editing sw]
|
|
or EditDoc position: CommentId + 2 or 4 ints, or WeakRef
|
|
or Y_JSON_NULL (for sw non-text selections, effectively ignored)
|
|
|
|
Some confusing object relationships:
|
|
|
|
SwAnnotationWin -> Outliner -> OutlinerEditEng -> EditEngine -> ImpEditEngine -> EditDoc
|
|
-> OutlinerView -> EditView -> ImpEditView -> EditEngine
|
|
|
|
-> SidebarTextControl
|
|
|
|
### Undo
|
|
|
|
There was no Undo for edits inside comments anyway, only when losing the
|
|
focus a SwUndoFieldFromDoc is created.
|
|
|
|
There are 2 approaches how Undo could work: either let all the SwUndo
|
|
actions modify the yrs model, or use the yrs yundo_manager and ensure that
|
|
for every top-level SwUndo there is exactly one item in the yundo_manager's
|
|
stack, so that Undo/Redo will have the same effect in the sw model and
|
|
the yrs model.
|
|
|
|
Let's try if the second approach can be made to work.
|
|
|
|
The yundo_manager by default creates stack items based on a timer, so we
|
|
configure that to a 2^31 timeout and invoke yundo_manager_stop() to create
|
|
all the items manually.
|
|
|
|
yundo_manager_undo()/redo() etc internally create a YTransaction and commit
|
|
it, which will of course fail if one already exists!
|
|
|
|
The yundo_manager creates a new item also for things like cursor movements
|
|
(because we put the cursors in the same YDocument as the content); there is
|
|
a way to filter the changes by Branch, but that filtering happens when the
|
|
undo/redo is invoked, not when the stack item is created - so the
|
|
"temporary" stack item is always extended with yrs changes until a "real"
|
|
change that has a corresponding SwUndo happens and there is a corresponding
|
|
yundo_manager_stop() then.
|
|
|
|
There are still some corner cases where the 2 undo stacks aren't synced so
|
|
there are various workarounds like DummyUndo action or m_nTempUndoOffset
|
|
counter for these.
|
|
|
|
Also the SwUndoFieldFromDoc batch action is problematic: it is created when
|
|
the comment loses focus, but all editing operations in the comment are
|
|
inserted in the yrs model immediately as they happen - so if changes are
|
|
received from peers, the creation of the SwUndoFieldFromDoc must be forced
|
|
to happen before the peer changes are applied in the sw document model, to
|
|
keep the actions in order.
|
|
|
|
The comment id is used as a key in yrs ymap so it should be the same when
|
|
the Undo or Redo inserts a comment again, hence it's added to SwPostItField.
|
|
|
|
For edits that are received from peers, what happens is that all the
|
|
received changes must be grouped into one SwUndo (list) action because there
|
|
is going to be one yrs undo item; and invoking Undo will just send the
|
|
changes to peers and they will create a new undo stack item as the local
|
|
instance goes back in the undo stack, and it's not possible to do it
|
|
differently because the undo stack items may contain different changes on
|
|
each peer.
|
|
|