// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { ascendWhileSingleInline } from "./ascend"; import type { ChildNodeRange } from "./child-node-range"; import { areSiblingChildNodeRanges, coversWholeParent, mergeChildNodeRanges, nodeToChildNodeRange, } from "./child-node-range"; interface MergeMatch { mismatch: boolean; minimized: ChildNodeRange[]; } function createInitialMergeMatch(childNodeRange: ChildNodeRange): MergeMatch { return { mismatch: false, minimized: [childNodeRange], }; } /** * After an _inner match_, we right-reduce the existing matches * to see if any existing inner matches can be matched to one bigger match * * @example When surround with * Hello World will be merged to * Hello World */ const tryMergingTillMismatch = (base: Element) => ( { mismatch, minimized /* must be nonempty */ }: MergeMatch, childNodeRange: ChildNodeRange, ): MergeMatch => { if (mismatch) { return { mismatch, minimized: [childNodeRange, ...minimized], }; } else { const [nextChildNodeRange, ...restChildNodeRanges] = minimized; if ( areSiblingChildNodeRanges( childNodeRange, nextChildNodeRange, ) /* && !childNodeRange.parent === base */ ) { const mergedChildNodeRange = mergeChildNodeRanges( childNodeRange, nextChildNodeRange, ); const newChildNodeRange = coversWholeParent(mergedChildNodeRange) && mergedChildNodeRange.parent !== base ? nodeToChildNodeRange( ascendWhileSingleInline( mergedChildNodeRange.parent, base, ), ) : mergedChildNodeRange; return { mismatch, minimized: [newChildNodeRange, ...restChildNodeRanges], }; } else { return { mismatch: true, minimized: [childNodeRange, ...minimized], }; } } }; function getMergeMatcher(base: Element) { function mergeMatchInner( accu: ChildNodeRange[], childNodeRange: ChildNodeRange, ): ChildNodeRange[] { return [...accu].reduceRight( tryMergingTillMismatch(base), createInitialMergeMatch(childNodeRange), ).minimized; } return mergeMatchInner; } export function mergeMatchChildNodeRanges( ranges: ChildNodeRange[], base: Element, ): ChildNodeRange[] { return ranges.reduce(getMergeMatcher(base), []); }