import { Fun, Merger } from '@ephox/katamari'; import { PredicateFilter, type SimRange, type SugarElement, SugarNode, SugarText, Traverse } from '@ephox/sugar'; import * as fc from 'fast-check'; export interface SelectionExclusions { containers: (container: SugarElement) => boolean; } const defaultExclusions: SelectionExclusions = { containers: Fun.never /* Maybe support offsets later if it makes sense to do so */ }; const getEnd = (target: SugarElement): number => // Probably do this more efficiently SugarNode.isText(target) ? SugarText.get(target).length : Traverse.children(target).length; const gChooseIn = (target: SugarElement): fc.Arbitrary<{ element: SugarElement; offset: number }> => { const offsets = getEnd(target); return fc.integer({ min: 0, max: offsets }).map((offset) => ({ element: target, offset })); }; const gChooseFrom = (root: SugarElement, exclusions: SelectionExclusions) => { const rootArray = exclusions.containers(root) ? [] : [ root ]; const everything = PredicateFilter.descendants(root, Fun.not(exclusions.containers)).concat(rootArray); return fc.constantFrom(...(everything.length > 0 ? everything : [ root ])).chain(gChooseIn); }; const selection = (root: SugarElement, rawExclusions: SelectionExclusions): fc.Arbitrary => { const exclusions: SelectionExclusions = Merger.deepMerge(defaultExclusions, rawExclusions); return gChooseFrom(root, exclusions).chain((start) => gChooseFrom(root, exclusions).map((finish): SimRange => ({ start: start.element, soffset: start.offset, finish: finish.element, foffset: finish.offset }))); }; export { selection };