Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I/8054: Model#insertContent should use fewer operations #8773

Merged
merged 5 commits into from
Jan 15, 2021
Merged

Conversation

niegowski
Copy link
Contributor

@niegowski niegowski commented Jan 7, 2021

Suggested merge commit message (convention)

Other (engine): Optimized Model#insertContent() to use as few operations as possible to reduce the time needed to handle pasting large content into the editor. Closes #8054. Closes #715.


Additional information

Copy link
Contributor

@scofalik scofalik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Minor comments mostly on code style and requests for better in-code comments.

I had some doubts but I think I eventually understood everything correctly. Please clarify where I commented.

handleNodes( nodes, parentContext ) {
nodes = Array.from( nodes );
handleNodes( nodes ) {
for ( const node of Array.from( nodes ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nodes param is iterable, you shouldn't need to make an array out of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those nodes are inserted one by one to the temporary document fragment (it removes them from the original collection) so it invalidates the iterator.

// <p>x^y</p> + <p>z</p> => <p>x</p>^<p>y</p> + <p>z</p> => <p>x</p><p>z</p><p>y</p> => <p>xz[]y</p>
// but:
// <p>x</p><p>^</p><p>z</p> + <p>y</p> => <p>x</p><p>y</p><p>z</p> (no merging)
// <p>x</p>[<img>]<p>z</p> + <p>y</p> => <p>x</p><p>y</p><p>z</p> (no merging, note: after running deleteContents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Insertion constructor you only pass position. I am not sure if it makes sense to describe this scenario here. I understand that you wanted to show other examples but it might be even confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved from the previous location, I'll add this to one of the merging methods.

Comment on lines 324 to 332
if ( this._documentFragment.getChild( 0 ) == this._firstNode ) {
this.writer.insert( this._firstNode, this.position );
this.position = livePosition.toPosition();
}

// Insert the remaining nodes from document fragment.
if ( !this._documentFragment.isEmpty ) {
this.writer.insert( this._documentFragment, this.position );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so I finally understood that this whole fragment is like that so we have an insert operation only for the really first node that I ever inserted in the whole insertContent process, because only the first node can be merged.

I didn't understand why we check if if ( this._documentFragment.getChild( 0 ) == this._firstNode ) { instead of doing it always but then I realized that there can be multiple calls to _insertPartialFragment() during one insertion. But then I didn't understand why it always checks with the very first node, but now I understand that's because only the very first node can be merged.

Yeah this is optimal but a little confusing.

How about changing the comment to:

// If the very first node of the whole insertion process is inserted, insert it separately for OT reasons (undo).
// Note: there can be multiple calls to `_insertPartialFragment()` during one insertion process.
// Note: only the very first node can be merged so we have to do separate operation only for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


this.writer.merge( mergePosLeft );
if ( this._firstNode === this._lastNode ) {
this._firstNode = this._lastNode = mergePosLeft.nodeBefore;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do it in two lines of code :).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But actually, I wonder... Why/when we want to change _firstNode? Isn't it that we want to merge only the very first node? Even if we do some splitting etc.?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think I finally got it. Is it a case of:

<p>A^A</p>

paste <p>X</p>

<p>A</p>^<p>A</p>
<p>A</p><p>X</p><p>A</p>
<p>AX</p><p>A</p>
<p>AXA</p>

?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in this case we would have keep references to wrong (removed due to merge) node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment about that.

// <p>x</p>[]<p>y</p> => <p>x[]</p><p>y</p>
this.position = Position._createAt( mergePosRight.nodeBefore, 'end' );

// OK: <p>xx[]</p> + <p>yy</p> => <p>xx[]yy</p> (when sticks to previous)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is old comment, but I'd add this before it, because I didn't know what this example talks about:

// Explanation of setting position stickiness to `'toPrevious'`:

// NOK: <p>xx[]</p> + <p>yy</p> => <p>xxyy[]</p> (when sticks to next)
const livePosition = LivePosition.fromPosition( this.position, 'toPrevious' );

// See comment above on moving `_affectedStart`.
Copy link
Contributor

@scofalik scofalik Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to "See comment in mergeSiblingsOfFirstNode() ...".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW. shouldn't this be called mergeSiblingOfFirstNode() (no "s") or simply mergeOnLeft()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@scofalik
Copy link
Contributor

Waiting for feedeback from @ckeditor/qa-team

@Mgsy
Copy link
Member

Mgsy commented Jan 15, 2021

I've found a regression in Track Changes:

  1. Turn on track changes.
  2. Create the following selection:
<paragraph>Something.</paragraph>
<paragraph>Somet[hing.</paragraph>
<blockquote>Somet]hing.</blockquote>

3. Copy it and paste at the end of the first paragraph.
4. Select all.
5. Delete.
6. Undo 2x.
7. Redo 2x.

ckeditorerror.js:64 Uncaught CKEditorError: model-createpositionat-offset-required
Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-model-createpositionat-offset-required
    at Function._createAt (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:88090:11)
    at new Range (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:88382:66)
    at Range._getTransformedByMove (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:89193:17)
    at Range._getTransformedByMoveOperation (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:88975:15)
    at http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:85616:93
    at transform (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:84553:10)
    at transformSets (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:84788:19)
    at RedoCommand._undo (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:157104:138)
    at Object.callback (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:157208:9)
    at Model._runPendingChanges (http://localhost:8125/ckeditor5/external/collaboration-features/packages/ckeditor5-comments/tests/manual/annotations.js:81402:58)

@niegowski
Copy link
Contributor Author

I've found a regression in Track Changes:

  1. Turn on track changes.
  2. Create the following selection:
<paragraph>Something.</paragraph>
<paragraph>Somet[hing.</paragraph>
<blockquote>Somet]hing.</blockquote>
  1. Copy it and paste at the end of the first paragraph.
  2. Select all.
  3. Delete.
  4. Undo 2x.
  5. Redo 2x.

This was caused by a changed order of operations, before this change it was:

  1. insert the first node
  2. merge the first node with the previous one
  3. insert the second node
  4. merge the second node with the next one

In this PR:

  1. insert the first node
  2. insert the second node
  3. merge the first node with the previous one
  4. merge the second node with the next one

I've just pushed the fix for that and for other review comments.

* <p>x^y</p> + <p>z</p> => <p>x</p>^<p>y</p> + <p>z</p> => <p>x</p><p>z</p><p>y</p> => <p>xz[]y</p>
* but:
* <p>x</p><p>^</p><p>z</p> + <p>y</p> => <p>x</p><p>y</p><p>z</p> (no merging)
* <p>x</p>[<img>]<p>z</p> + <p>y</p> => <p>x</p><p>y</p><p>z</p> (no merging, note: after running deleteContents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think this should be here. This is just incorrect example, it shouldn't be here. Insertion class does not operate on a range.

I think this whole comment (I mean all three examples without the last) should be somewhere else (handleNodes()? or Insertion class description?).

@scofalik scofalik merged commit d97206c into master Jan 15, 2021
@scofalik scofalik deleted the i/8054 branch January 15, 2021 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants