Skip to content

Commit

Permalink
Allow users to pass multi nodes when calling Tree.edit (#579)
Browse files Browse the repository at this point in the history
* Allow users to pass multi nodes when calling Tree.edit 

* Add nodes validation to Tree.edit
  • Loading branch information
JOOHOJANG authored Jul 13, 2023
1 parent 2b15ac5 commit 12d410f
Show file tree
Hide file tree
Showing 10 changed files with 744 additions and 342 deletions.
26 changes: 24 additions & 2 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ func fromTreeEdit(pbTreeEdit *api.Operation_TreeEdit) (*operations.TreeEdit, err
return nil, err
}

node, err := FromTreeNodes(pbTreeEdit.Content)
nodes, err := FromTreeNodesWhenEdit(pbTreeEdit.Contents)
if err != nil {
return nil, err
}
Expand All @@ -550,7 +550,7 @@ func fromTreeEdit(pbTreeEdit *api.Operation_TreeEdit) (*operations.TreeEdit, err
parentCreatedAt,
from,
to,
node,
nodes,
executedAt,
), nil
}
Expand Down Expand Up @@ -647,6 +647,28 @@ func FromTreeNodes(pbNodes []*api.TreeNode) (*crdt.TreeNode, error) {
return crdt.NewTree(root, nil).Root(), nil
}

// FromTreeNodesWhenEdit converts protobuf tree nodes to array of crdt.TreeNode.
// in each element in array, the last node in slice is the root node, because the slice is in post-order.
func FromTreeNodesWhenEdit(pbNodes []*api.TreeNodes) ([]*crdt.TreeNode, error) {
if len(pbNodes) == 0 {
return nil, nil
}

var treeNodes []*crdt.TreeNode

for _, pbNode := range pbNodes {
treeNode, err := FromTreeNodes(pbNode.Content)

if err != nil {
return nil, err
}

treeNodes = append(treeNodes, treeNode)
}

return treeNodes, nil
}

func fromTreeNode(pbNode *api.TreeNode) (*crdt.TreeNode, error) {
pos, err := fromTreePos(pbNode.Pos)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions api/converter/to_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,26 @@ func ToTreeNodes(treeNode *crdt.TreeNode) []*api.TreeNode {
return pbTreeNodes
}

// ToTreeNodesWhenEdit converts a TreeNodes to a slice of two-dimensional array of TreeNodes in post-order traversal.
func ToTreeNodesWhenEdit(treeNodes []*crdt.TreeNode) []*api.TreeNodes {
pbTreeNodes := make([]*api.TreeNodes, len(treeNodes))

if len(treeNodes) == 0 {
return pbTreeNodes
}

for i, treeNode := range treeNodes {
var pbTreeNode []*api.TreeNode

pbTreeNode = append(pbTreeNode, ToTreeNodes(treeNode)[:]...)

pbTreeNodes[i] = &api.TreeNodes{}
pbTreeNodes[i].Content = append(pbTreeNodes[i].Content, pbTreeNode[:]...)
}

return pbTreeNodes
}

func toTreeNode(treeNode *crdt.TreeNode, depth int) *api.TreeNode {
var attrs map[string]*api.NodeAttr
if treeNode.Attrs != nil {
Expand Down
2 changes: 1 addition & 1 deletion api/converter/to_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func toTreeEdit(e *operations.TreeEdit) (*api.Operation_TreeEdit_, error) {
ParentCreatedAt: ToTimeTicket(e.ParentCreatedAt()),
From: toTreePos(e.FromPos()),
To: toTreePos(e.ToPos()),
Content: ToTreeNodes(e.Content()),
Contents: ToTreeNodesWhenEdit(e.Contents()),
ExecutedAt: ToTimeTicket(e.ExecutedAt()),
},
}, nil
Expand Down
560 changes: 377 additions & 183 deletions api/yorkie/v1/resources.pb.go

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion api/yorkie/v1/resources.proto
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ message Operation {
TimeTicket parent_created_at = 1;
TreePos from = 2;
TreePos to = 3;
repeated TreeNode content = 4;
repeated TreeNodes contents = 4;
TimeTicket executed_at = 5;
}
message TreeStyle {
Expand Down Expand Up @@ -233,6 +233,10 @@ message TreeNode {
map<string, NodeAttr> attributes = 7;
}

message TreeNodes {
repeated TreeNode content = 1;
}

message TreePos {
TimeTicket created_at = 1;
int32 offset = 2;
Expand Down
52 changes: 31 additions & 21 deletions pkg/document/crdt/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ func (t *Tree) ToXML() string {

// EditByIndex edits the given range with the given value.
// This method uses indexes instead of a pair of TreePos for testing.
func (t *Tree) EditByIndex(start, end int, content *TreeNode, editedAt *time.Ticket) error {
func (t *Tree) EditByIndex(start, end int, contents []*TreeNode, editedAt *time.Ticket) error {
fromPos, err := t.FindPos(start)
if err != nil {
return err
Expand All @@ -483,7 +483,7 @@ func (t *Tree) EditByIndex(start, end int, content *TreeNode, editedAt *time.Tic
return err
}

return t.Edit(fromPos, toPos, content, editedAt)
return t.Edit(fromPos, toPos, contents, editedAt)
}

// FindPos finds the position of the given index in the tree.
Expand All @@ -501,7 +501,7 @@ func (t *Tree) FindPos(offset int) (*TreePos, error) {

// Edit edits the tree with the given range and content.
// If the content is undefined, the range will be removed.
func (t *Tree) Edit(from, to *TreePos, content *TreeNode, editedAt *time.Ticket) error {
func (t *Tree) Edit(from, to *TreePos, contents []*TreeNode, editedAt *time.Ticket) error {
// 01. split text nodes at the given range if needed.
toPos, toRight, err := t.findTreePosWithSplitText(to, editedAt)
if err != nil {
Expand Down Expand Up @@ -567,29 +567,39 @@ func (t *Tree) Edit(from, to *TreePos, content *TreeNode, editedAt *time.Ticket)
}

// 03. insert the given node at the given position.
if content != nil {
// 03-1. insert the content nodes to the list.
if len(contents) != 0 {

previous := fromRight.Prev
index.TraverseNode(content.IndexTreeNode, func(node *index.Node[*TreeNode], depth int) {
t.InsertAfter(previous, node.Value)
previous = node.Value
})

// 03-2. insert the content nodes to the tree.
if fromPos.Node.IsText() {
if fromPos.Offset == 0 {
if err := fromPos.Node.Parent.InsertBefore(content.IndexTreeNode, fromPos.Node); err != nil {
return err
offset := fromPos.Offset
node := fromPos.Node

for _, content := range contents {
// 03-1. insert the content nodes to the list.
index.TraverseNode(content.IndexTreeNode, func(node *index.Node[*TreeNode], depth int) {
t.InsertAfter(previous, node.Value)
previous = node.Value
})

// 03-2. insert the content nodes to the tree.
if node.IsText() {
// if `contents` is consist of text nodes, then there'll be only one element in `contents`
// thus, there's no need to update fromPos
if fromPos.Offset == 0 {
if err := node.Parent.InsertBefore(content.IndexTreeNode, node); err != nil {
return err
}
} else {
if err := node.Parent.InsertAfter(content.IndexTreeNode, node); err != nil {
return err
}
}
} else {
if err := fromPos.Node.Parent.InsertAfter(content.IndexTreeNode, fromPos.Node); err != nil {
target := node
if err := target.InsertAt(content.IndexTreeNode, offset+1); err != nil {
return err
}
}
} else {
target := fromPos.Node
if err := target.InsertAt(content.IndexTreeNode, fromPos.Offset+1); err != nil {
return err

offset++
}
}
}
Expand Down
Loading

0 comments on commit 12d410f

Please sign in to comment.