materializePositionAt method

Materializes node at the exact position already computed by a relative subtree cursor.

Native reusable nodes carry relative padding and size, and their absolute position is the cursor's accumulated byte offset. Re-applying every edit to the node's original absolute coordinates is not equivalent after a sequence of replacements and inverse edits, so incremental parsing must preserve the cursor's position here.

Implementation

GeneratedTreeSitterNode materializePositionAt(
  GeneratedTreeSitterNode node,
  GeneratedTreeSitterNodePosition position,
) {
  final cache = _materializedPositionCaches[this] ??=
      Map<GeneratedTreeSitterNode, GeneratedTreeSitterNode>.identity();
  final cached = cache[node];
  if (cached != null &&
      cached.startByte == position.startByte &&
      cached.endByte == position.endByte &&
      _samePoint(cached.startPoint, position.startPoint) &&
      _samePoint(cached.endPoint, position.endPoint)) {
    return cached;
  }
  final positionChanged =
      position.startByte != node.startByte ||
      position.endByte != node.endByte ||
      !_samePoint(position.startPoint, node.startPoint) ||
      !_samePoint(position.endPoint, node.endPoint);
  if (!hasChangesFor(node) && !positionChanged) return node;

  final children = <GeneratedTreeSitterNode>[];
  var childEndByte = position.startByte;
  var childEndPoint = position.startPoint;
  for (var index = 0; index < node.children.length; index++) {
    final child = node.children[index];
    final childState = editedSubtreeStateOf(child);
    // A parent's start excludes the first child's padding. Every later
    // child begins after its own padding relative to the preceding end.
    final childStartByte = index == 0
        ? position.startByte
        : childEndByte + childState.paddingBytes;
    final childStartPoint = index == 0
        ? position.startPoint
        : _pointAdd(childEndPoint, childState.paddingExtent);
    final childPosition = GeneratedTreeSitterNodePosition(
      startByte: childStartByte,
      endByte: childStartByte + childState.sizeBytes,
      startPoint: childStartPoint,
      endPoint: _pointAdd(childStartPoint, childState.sizeExtent),
    );
    children.add(materializePositionAt(child, childPosition));
    childEndByte = childPosition.endByte;
    childEndPoint = childPosition.endPoint;
  }
  var childrenChanged = false;
  for (var index = 0; index < children.length; index++) {
    if (!identical(children[index], node.children[index])) {
      childrenChanged = true;
      break;
    }
  }
  final editState = editedSubtreeStateOf(node);
  final metricsChanged =
      editState.paddingBytes != node.paddingBytes ||
      !_samePoint(editState.paddingExtent, node.paddingExtent) ||
      editState.sizeBytes != node.sizeBytes ||
      !_samePoint(editState.sizeExtent, node.sizeExtent) ||
      editState.hasChanges != node.hasChanges;
  if (!childrenChanged && !positionChanged && !metricsChanged) {
    return node;
  }
  final materialized = node.withEditedMetricsAndChildren(
    startByte: position.startByte,
    endByte: position.endByte,
    startPoint: position.startPoint,
    endPoint: position.endPoint,
    paddingBytes: editState.paddingBytes,
    paddingExtent: editState.paddingExtent,
    sizeBytes: editState.sizeBytes,
    sizeExtent: editState.sizeExtent,
    hasChanges: editState.hasChanges,
    children: children,
  );
  cache[node] = materialized;
  return materialized;
}