Skip to content

Commit

Permalink
Implement merge elements in Tree.Edit
Browse files Browse the repository at this point in the history
  • Loading branch information
hackerwins committed Nov 2, 2023
1 parent dd9be29 commit d7a4b9e
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 31 deletions.
31 changes: 27 additions & 4 deletions src/document/crdt/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ export class CRDTTree extends CRDTGCElement {
});

const toBeRemoveds: Array<CRDTTreeNode> = [];
const toBeMovedsToFromParent: Array<CRDTTreeNode> = [];
const latestCreatedAtMap = new Map<string, TimeTicket>();

this.traverseInPosRange(
Expand All @@ -704,12 +705,31 @@ export class CRDTTree extends CRDTGCElement {
toParent,
toLeft,
(node, contain) => {
// If node is a element node and half-contained in the range,
// it should not be removed.
if (!node.isText && contain != TagContained.All) {
// NOTE(hackerwins): If the node overlaps as a closing tag with the
// range then we need to keep the node.
if (!node.isText && contain == TagContained.Closing) {
return;
}

// NOTE(hackerwins): If the node overlaps as an opening tag with the
// range then we need to move the remaing children to fromParent.
// TODO(hackerwins): Define more clearly mergable rules between fromParent
// and toParent. For now, if fromParent and toParent are the same
// type, then we can merge them.
if (
!node.isText &&
contain == TagContained.Opening &&
fromParent.type === toParent.type
) {
for (const child of node.children) {
if (toBeRemoveds.includes(child)) {
continue;
}

toBeMovedsToFromParent.push(child);
}
}

const actorID = node.getCreatedAt().getActorID()!;
const latestCreatedAt = latestCreatedAtMapByActor
? latestCreatedAtMapByActor!.has(actorID!)
Expand All @@ -732,12 +752,15 @@ export class CRDTTree extends CRDTGCElement {

for (const node of toBeRemoveds) {
node.remove(editedAt);

if (node.isRemoved) {
this.removedNodeMap.set(node.id.toIDString(), node);
}
}

for (const node of toBeMovedsToFromParent) {
fromParent.insertAt(node, fromParent._children.length);
}

// 03. insert the given node at the given position.
if (contents?.length) {
let leftInChildren = fromLeft; // tree
Expand Down
8 changes: 1 addition & 7 deletions test/integration/tree_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -963,13 +963,7 @@ describe('Tree.edit', function () {
);

doc.update((root) => root.t.edit(2, 18));
assert.equal(
doc.getRoot().t.toXML(),
/*html*/ `<doc><p>a</p><p>f</p></doc>`,
);

// TODO(sejongk): Use the below assertion after implementing Tree.Move.
// assert.equal(doc.getRoot().t.toXML(), /*html*/ `<doc><p>af</p></doc>`);
assert.equal(doc.getRoot().t.toXML(), /*html*/ `<doc><p>af</p></doc>`);
});
});

Expand Down
38 changes: 18 additions & 20 deletions test/unit/document/crdt/tree_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('CRDTTreeNode', function () {
});

// NOTE: To see the XML string as highlighted, install es6-string-html plugin in VSCode.
describe('CRDTTree', function () {
describe('CRDTTree.Edit', function () {
it('Can inserts nodes with edit', function () {
// 0
// <root> </root>
Expand Down Expand Up @@ -250,7 +250,7 @@ describe('CRDTTree', function () {
});
});

describe.skip('Tree.split', function () {
describe.skip('CRDTTree.Split', function () {
it('Can split text nodes', function () {
// 00. Create a tree with 2 paragraphs.
// 0 1 6 11
Expand Down Expand Up @@ -417,7 +417,7 @@ describe.skip('Tree.split', function () {
});
});

describe('Tree.move', function () {
describe('CRDTTree.Merge', function () {
it('Can delete nodes between element nodes with edit', function () {
// 01. Create a tree with 2 paragraphs.
// 0 1 2 3 4 5 6 7 8
Expand All @@ -444,23 +444,21 @@ describe('Tree.move', function () {
// 0 1 2 3 4
// <root> <p> a d </p> </root>
tree.editByIndex([2, 6], undefined, issueTime());
assert.deepEqual(tree.toXML(), /*html*/ `<root><p>a</p><p>d</p></root>`);
// TODO(sejongk): Use the below assertion after implementing Tree.Move.
// assert.deepEqual(tree.toXML(), /*html*/ `<root><p>ad</p></root>`);

// const treeNode = tree.toTestTreeNode();
// assert.equal(treeNode.size, 4); // root
// assert.equal(treeNode.children![0].size, 2); // p
// assert.equal(treeNode.children![0].children![0].size, 1); // a
// assert.equal(treeNode.children![0].children![1].size, 1); // d

// // 03. insert a new text node at the start of the first paragraph.
// tree.editByIndex(
// [1, 1],
// [new CRDTTreeNode(issuePos(), 'text', '@')],
// issueTime(),
// );
// assert.deepEqual(tree.toXML(), /*html*/ `<root><p>@ad</p></root>`);
assert.deepEqual(tree.toXML(), /*html*/ `<root><p>ad</p></root>`);

const node = tree.toTestTreeNode();
assert.equal(node.size, 4); // root
assert.equal(node.children![0].size, 2); // p
assert.equal(node.children![0].children![0].size, 1); // a
assert.equal(node.children![0].children![1].size, 1); // d

// 03. insert a new text node at the start of the first paragraph.
tree.editByIndex(
[1, 1],
[new CRDTTreeNode(issuePos(), 'text', '@')],
issueTime(),
);
assert.deepEqual(tree.toXML(), /*html*/ `<root><p>@ad</p></root>`);
});

it.skip('Can merge different levels with edit', function () {
Expand Down

0 comments on commit d7a4b9e

Please sign in to comment.