splitAndApplyToLeaves function
({Fragment? firstModified, Fragment? lastModified})
splitAndApplyToLeaves(
- FluentDocument document,
- ResolvedSelection selection, {
- List<
Fragment> ? modifyBatch()?, - Fragment? modify(
- Fragment leaf
Splits fragments at selection boundaries, then calls modify on each
leaf fragment in the resulting range (skipping FluentImage).
Returns the first and last modified fragments, or nulls if none modified.
Implementation
({Fragment? firstModified, Fragment? lastModified}) splitAndApplyToLeaves(
FluentDocument document,
ResolvedSelection selection, {
List<Fragment>? Function(List<Fragment> leaves)? modifyBatch,
Fragment? Function(Fragment leaf)? modify,
}) {
Fragment? firstModified;
Fragment? lastModified;
for (final node in selection.nodes) {
final container = node.container;
late Fragment actualStartFrag;
late Fragment actualEndFrag;
final startParent = findParentCached(document, node.startFragment);
final endParent = findParentCached(document, node.endFragment);
if (node.startFragment.id == node.endFragment.id) {
final frag = node.startFragment;
final len = frag.text.length;
var start = node.startOffset.clamp(0, len);
var end = node.endOffset.clamp(0, len);
if (start > end) {
final tmp = start;
start = end;
end = tmp;
}
if (start == end) {
actualStartFrag = frag;
actualEndFrag = frag;
} else if (start > 0 && end < len) {
final before = frag.text.substring(0, start);
final mid = frag.text.substring(start, end);
final after = frag.text.substring(end);
frag.text = before;
final midFrag = FragmentOperations.cloneFragment(frag, text: mid);
if (startParent != null) {
insertAfter(startParent, frag, midFrag);
document.updateParentCache(midFrag.id, startParent.id);
final afterFrag = FragmentOperations.cloneFragment(frag, text: after);
insertAfter(startParent, midFrag, afterFrag);
document.updateParentCache(afterFrag.id, startParent.id);
}
actualStartFrag = midFrag;
actualEndFrag = midFrag;
} else if (start > 0) {
final before = frag.text.substring(0, start);
final after = frag.text.substring(start);
frag.text = before;
final newFrag = FragmentOperations.cloneFragment(frag, text: after);
if (startParent != null) {
insertAfter(startParent, frag, newFrag);
document.updateParentCache(newFrag.id, startParent.id);
}
actualStartFrag = newFrag;
actualEndFrag = newFrag;
} else if (end < len) {
final selected = frag.text.substring(0, end);
final after = frag.text.substring(end);
frag.text = selected;
final afterFrag = FragmentOperations.cloneFragment(frag, text: after);
if (startParent != null) {
insertAfter(startParent, frag, afterFrag);
document.updateParentCache(afterFrag.id, startParent.id);
}
actualStartFrag = frag;
actualEndFrag = frag;
} else {
actualStartFrag = frag;
actualEndFrag = frag;
}
} else {
// Different fragments: split first at startOffset, and last at endOffset
final first = node.startFragment;
final last = node.endFragment;
final sOffset = node.startOffset.clamp(0, first.text.length);
if (sOffset > 0 && sOffset < first.text.length) {
final before = first.text.substring(0, sOffset);
final selected = first.text.substring(sOffset);
first.text = before;
final selectedFrag = FragmentOperations.cloneFragment(first, text: selected);
if (startParent != null) {
insertAfter(startParent, first, selectedFrag);
document.updateParentCache(selectedFrag.id, startParent.id);
}
actualStartFrag = selectedFrag;
} else if (sOffset == first.text.length) {
actualStartFrag = first; // Handled below by advancing startIdx
} else {
actualStartFrag = first;
}
final eOffset = node.endOffset.clamp(0, last.text.length);
if (eOffset > 0 && eOffset < last.text.length) {
final selected = last.text.substring(0, eOffset);
final after = last.text.substring(eOffset);
last.text = selected;
final afterFrag = FragmentOperations.cloneFragment(last, text: after);
if (endParent != null) {
insertAfter(endParent, last, afterFrag);
document.updateParentCache(afterFrag.id, endParent.id);
}
actualEndFrag = last;
} else if (eOffset == 0) {
actualEndFrag = last; // Handled below by retracting endIdx
} else {
actualEndFrag = last;
}
}
final leaves = FragmentOperations.collectLeafFragments(container as FNode);
int startIdx = leaves.indexWhere((l) => l.id == actualStartFrag.id);
int endIdx = leaves.indexWhere((l) => l.id == actualEndFrag.id);
if (node.startFragment.id != node.endFragment.id) {
final sOffset = node.startOffset.clamp(0, node.startFragment.text.length);
final eOffset = node.endOffset.clamp(0, node.endFragment.text.length);
if (sOffset >= node.startFragment.text.length && actualStartFrag.id == node.startFragment.id) {
startIdx++;
}
if (eOffset <= 0 && actualEndFrag.id == node.endFragment.id) {
endIdx--;
}
}
if (startIdx <= endIdx && startIdx >= 0 && endIdx < leaves.length) {
final leavesToModify = <Fragment>[];
for (int i = startIdx; i <= endIdx; i++) {
final leaf = leaves[i];
if (leaf is! FluentImage) {
if (leaf.styles?.contains(document.suggestionStyleHook.deletionTag) != true) {
leavesToModify.add(leaf);
}
}
}
if (leavesToModify.isNotEmpty) {
final res = modifyBatch?.call(leavesToModify);
if (res != null && res.isNotEmpty) {
firstModified ??= res.first;
lastModified = res.last;
} else {
for (final leaf in leavesToModify) {
final modLeaf = modify?.call(leaf);
if (modLeaf != null) {
firstModified ??= modLeaf;
lastModified = modLeaf;
}
}
}
}
}
}
return (firstModified: firstModified, lastModified: lastModified);
}