List<T> (@NotNull DocumentEvent e, int reTargetShift, @NotNull List<? extends IntervalNode<T>> affected) { List<T> invalidated = affected.isEmpty() ? Collections.emptyList() : new ArrayList<>(affected.size()); // reverse direction to visit leaves first - it's cheaper to compute maxEndOf for them first for (int i = affected.size() - 1; i >= 0; i--) { IntervalNode<T> node = affected.get(i); // assumption: interval.getEndOffset() will never be accessed during remove() int startOffset = node.intervalStart(); int endOffset = node.intervalEnd(); removeNode(node); checkMax(false); node.setParent(null); node.setLeft(null); node.setRight(null); node.setValid(true); if (reTargetShift == 0) { // we can do it because all the deltas up from the root to this node were cleared in the collectAffectedMarkersAndShiftSubtrees node.clearDelta(); assert node.intervalStart() == startOffset; assert node.intervalEnd() == endOffset; } else { node.changeDelta(reTargetShift); pushDelta(node); } } checkMax(true); for (IntervalNode<T> node : affected) { RangeMarkerImpl marker = getAnyNodeMarker(node, invalidated); if (marker == null) continue; // node remains removed from the tree if (reTargetShift == 0) { marker.onDocumentChanged(e); } else { marker.onReTarget(e); } if (marker.isValid()) { findOrInsertWithIntervals(node); } else { node.setValid(false); ((RMNode<?>)node).onRemoved(); } if (!node.isValid()) { node.processAliveKeys(t->invalidated.add(t)); } } checkMax(true); return invalidated; }