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

Allow users to pass multi nodes when calling Tree.edit #579

Merged
merged 11 commits into from
Jul 13, 2023
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