commitSaveState method

SaveStateResult commitSaveState(
  1. FluentDocument document
)

Called AFTER a mutation (from FluentDocument.updateContent). Compares the current top-level nodes with the pending snapshot, builds a minimal DocumentDelta, and pushes it onto the undo stack. Returns a SaveStateResult indicating whether a new delta was created, merged into an existing delta, or if no changes occurred.

Implementation

SaveStateResult commitSaveState(FluentDocument document) {
  if (_isRestoringState || _pending == null) return SaveStateResult.noChange;

  final pending = _pending!;
  final newNodes = document.content.nodes;
  final oldNodes = pending.oldTopLevelNodes;

  final changes = <NodeChange>[];

  if (newNodes.length == oldNodes.length) {
    for (int i = 0; i < oldNodes.length; i++) {
      final oldJson = oldNodes[i];
      final newNode = newNodes[i];

      final newJson = newNode.toJson();
      if (!_mapsEqual(oldJson, newJson)) {
        final frozenNewJson = _deepCopyJsonMap(newJson);
        changes.add(NodeChange(
          index: i,
          oldJson: oldJson,
          newJson: frozenNewJson,
        ));
      }
    }
  } else {
    final maxLen = oldNodes.length > newNodes.length
        ? oldNodes.length
        : newNodes.length;
    for (int i = 0; i < maxLen; i++) {
      final oldJson = i < oldNodes.length ? oldNodes[i] : null;
      final newNode = i < newNodes.length ? newNodes[i] : null;
      final newJson = newNode?.toJson();
      if (oldJson == null || newJson == null || !_mapsEqual(oldJson, newJson)) {
        changes.add(NodeChange(
          index: i,
          oldJson: oldJson ?? <String, dynamic>{},
          newJson: newJson != null ? _deepCopyJsonMap(newJson) : <String, dynamic>{},
        ));
      }
    }
  }

  if (changes.isEmpty) {
    _pending = null;
    return SaveStateResult.noChange;
  }

  final delta = NodeReplaceDelta(
    description: pending.description,
    timestamp: pending.timestamp,
    changes: changes,
    oldCursor: pending.oldCursor,
    newCursor: CursorSnapshot.fromDocument(document),
  );

  if (_currentGroupDescription != null &&
      _currentGroupDescription == pending.description &&
      _undoStack.isNotEmpty &&
      !_forceNewAction) {
    final lastDelta = _undoStack.last;
    if (lastDelta is NodeReplaceDelta &&
        lastDelta.description == pending.description) {
      final mergedChanges = <int, NodeChange>{};
      for (final c in lastDelta.changes) {
        mergedChanges[c.index] = c;
      }
      for (final c in delta.changes) {
        final existing = mergedChanges[c.index];
        if (existing != null) {
          mergedChanges[c.index] = NodeChange(
            index: c.index,
            oldJson: existing.oldJson,
            newJson: c.newJson,
          );
        } else {
          mergedChanges[c.index] = c;
        }
      }
      final mergedDelta = NodeReplaceDelta(
        description: pending.description,
        timestamp: lastDelta.timestamp,
        changes: mergedChanges.values.toList(),
        oldCursor: lastDelta.oldCursor,
        newCursor: delta.newCursor,
      );
      _undoStack.last = mergedDelta;
      _pending = null;
      _redoStack.clear();
      _updateJsonCacheFromChanges(document, mergedChanges.values.toList());
      _enforceMemoryLimit();
      return SaveStateResult.merged;
    }
  }

  _undoStack.add(delta);
  _redoStack.clear();
  _pending = null;
  _updateJsonCacheFromChanges(document, changes);
  _enforceMemoryLimit();
  return SaveStateResult.created;
}