import type { Universe } from '@ephox/boss'; import { Arr, Fun, Optional } from '@ephox/katamari'; interface SubsetAncestors { readonly firstpath: E[]; readonly secondpath: E[]; readonly shared: Optional; } const eq = (universe: Universe, item: E): (e: E) => boolean => { return Fun.curry(universe.eq, item); }; const unsafeSubset = (universe: Universe, common: E, ps1: E[], ps2: E[]): Optional => { const children = universe.property().children(common); if (universe.eq(common, ps1[0])) { return Optional.some([ ps1[0] ]); } if (universe.eq(common, ps2[0])) { return Optional.some([ ps2[0] ]); } const finder = (ps: E[]) => { // ps is calculated bottom-up, but logically we're searching top-down const topDown = Arr.reverse(ps); // find the child of common in the ps array const index = Arr.findIndex(topDown, eq(universe, common)).getOr(-1); const item = index < topDown.length - 1 ? topDown[index + 1] : topDown[index]; // find the index of that child in the common children return Arr.findIndex(children, eq(universe, item)); }; const startIndex = finder(ps1); const endIndex = finder(ps2); // Return all common children between first and last return startIndex.bind((sIndex) => { return endIndex.map((eIndex) => { // This is required because the range could be backwards. const first = Math.min(sIndex, eIndex); const last = Math.max(sIndex, eIndex); return children.slice(first, last + 1); }); }); }; // Note: this can be exported if it is required in the future. const ancestors = (universe: Universe, start: E, end: E, isRoot: (x: E) => boolean = Fun.never): SubsetAncestors => { // Inefficient if no isRoot is supplied. // TODO: Andy knows there is a graph-based algorithm to find a common parent, but can't remember it // This also includes something to get the subset after finding the common parent const ps1 = [ start ].concat(universe.up().all(start)); const ps2 = [ end ].concat(universe.up().all(end)); const prune = (path: E[]) => { const index = Arr.findIndex(path, isRoot); return index.fold(() => { return path; }, (ind) => { return path.slice(0, ind + 1); }); }; const pruned1 = prune(ps1); const pruned2 = prune(ps2); const shared = Arr.find(pruned1, (x) => { return Arr.exists(pruned2, eq(universe, x)); }); return { firstpath: pruned1, secondpath: pruned2, shared }; }; /** * Find the common element in the parents of start and end. * * Then return all children of the common element such that start and end are included. */ const subset = (universe: Universe, start: E, end: E): Optional => { const ancs = ancestors(universe, start, end); return ancs.shared.bind((shared) => { return unsafeSubset(universe, shared, ancs.firstpath, ancs.secondpath); }); }; export { subset, ancestors };