diffNodes function

List<Operation> diffNodes(
  1. Node oldNode,
  2. Node newNode
)

Implementation

List<Operation> diffNodes(Node oldNode, Node newNode) {
  List<Operation> operations = [];

  if (!_equality.equals(oldNode.attributes, newNode.attributes)) {
    operations.add(
      UpdateOperation(oldNode.path, newNode.attributes, oldNode.attributes),
    );
  }

  final oldChildrenById = {
    for (final child in oldNode.children) child.id: child,
  };
  final newChildrenById = {
    for (final child in newNode.children) child.id: child,
  };

  // Identify insertions and updates
  for (final newChild in newNode.children) {
    final oldChild = oldChildrenById[newChild.id];
    if (oldChild == null) {
      // Insert operation
      operations.add(InsertOperation(newChild.path, [newChild]));
    } else {
      // Recursive diff for updates
      operations.addAll(diffNodes(oldChild, newChild));
    }
  }

  // Identify deletions
  oldChildrenById.keys
      .where((id) => !newChildrenById.containsKey(id))
      .forEach((id) {
    final oldChild = oldChildrenById[id]!;
    operations.add(DeleteOperation(oldChild.path, [oldChild]));
  });

  // Combine the operation in operations

  // 1. Insert operations can be combined if they are continuous
  final combinedOperations = <Operation>[];
  if (operations.isNotEmpty &&
      operations.every((element) => element is InsertOperation)) {
    operations.sorted((a, b) => a.path <= b.path ? -1 : 1);
    for (var i = 0; i < operations.length; i++) {
      final op = operations[i];
      if (combinedOperations.isEmpty) {
        combinedOperations.add(op);
      } else {
        if (op.path.equals(operations[i - 1].path.next)) {
          final lastOp = combinedOperations.removeLast();
          combinedOperations.add(
            InsertOperation(
              lastOp.path,
              [
                ...(lastOp as InsertOperation).nodes,
                ...(op as InsertOperation).nodes,
              ],
            ),
          );
        } else {
          combinedOperations.add(op);
        }
      }
    }
    return combinedOperations;
  }

  // 2. Delete operations can be combined if they are continuous
  if (operations.isNotEmpty &&
      operations.every((element) => element is DeleteOperation)) {
    operations.sorted((a, b) => a.path <= b.path ? -1 : 1);
    for (var i = 0; i < operations.length; i++) {
      final op = operations[i];
      if (combinedOperations.isEmpty) {
        combinedOperations.add(op);
      } else {
        if (op.path.equals(operations[i - 1].path.next)) {
          final lastOp = combinedOperations.removeLast();
          combinedOperations.add(
            DeleteOperation(
              lastOp.path,
              [
                ...(lastOp as DeleteOperation).nodes,
                ...(op as DeleteOperation).nodes,
              ],
            ),
          );
        } else {
          combinedOperations.add(op);
        }
      }
    }
    return combinedOperations;
  }

  return operations;
}