import type { Universe } from '@ephox/boss'; import { Adt, Optional } from '@ephox/katamari'; import { Descent, type Direction, Gather, Seeker, Spot, type SpotPoint, type Transition } from '@ephox/phoenix'; import * as Structure from '../api/general/Structure'; export interface TextSeekerPhase { fold: ( abort: () => T, kontinue: () => T, finish: (info: SpotPoint) => T ) => T; match: (branches: { abort: () => T; kontinue: () => T; finish: (info: SpotPoint) => T; }) => T; log: (label: string) => void; } export interface TextSeekerOutcome { fold: ( aborted: () => T, edge: (element: E) => T, success: (info: SpotPoint) => T ) => T; match: (branches: { aborted: () => T; edge: (element: E) => T; success: (info: SpotPoint) => T; }) => T; log: (label: string) => void; } const walkLeft = Gather.walkers().left(); const walkRight = Gather.walkers().right(); const phase: { abort: () => TextSeekerPhase; kontinue: () => TextSeekerPhase; finish: (info: SpotPoint) => TextSeekerPhase; } = Adt.generate([ { abort: [ ] }, { kontinue: [ ] }, { finish: [ 'info' ] } ]); export type TextSeekerPhaseConstructor = typeof phase; export type TextSeekerPhaseProcessor = (universe: Universe, phase: TextSeekerPhaseConstructor, item: E, text: string, offsetOption: Optional) => TextSeekerPhase; const outcome: { aborted: () => TextSeekerOutcome; edge: (element: E) => TextSeekerOutcome; success: (info: SpotPoint) => TextSeekerOutcome; } = Adt.generate([ { aborted: [] }, { edge: [ 'element' ] }, { success: [ 'info' ] } ]); const isBoundary = (universe: Universe, item: E) => { return Structure.isEmptyTag(universe, item) || universe.property().isBoundary(item); }; const repeat = (universe: Universe, item: E, mode: Transition, offsetOption: Optional, process: TextSeekerPhaseProcessor, walking: Direction, recent: Optional): TextSeekerOutcome => { const terminate = () => { return recent.fold>(outcome.aborted, outcome.edge); }; const recurse = (newRecent: Optional) => { return Gather.walk(universe, item, mode, walking).fold( terminate, (prev) => { return repeat(universe, prev.item, prev.mode, Optional.none(), process, walking, newRecent); } ); }; if (isBoundary(universe, item)) { return terminate(); } else if (!universe.property().isText(item)) { return recurse(recent); } else { const text = universe.property().getText(item); return process(universe, phase, item, text, offsetOption).fold( terminate, () => { return recurse(Optional.some(item)); }, outcome.success ); } }; const descendToLeft = (universe: Universe, item: E, offset: number, isRoot: (e: E) => boolean): Optional> => { const descended = Descent.toLeaf(universe, item, offset); if (universe.property().isText(item)) { return Optional.none>(); } else { return Seeker.left(universe, descended.element, universe.property().isText, isRoot).map((t) => { return Spot.point(t, universe.property().getText(t).length); }); } }; const descendToRight = (universe: Universe, item: E, offset: number, isRoot: (e: E) => boolean): Optional> => { const descended = Descent.toLeaf(universe, item, offset); if (universe.property().isText(item)) { return Optional.none>(); } else { return Seeker.right(universe, descended.element, universe.property().isText, isRoot).map((t) => { return Spot.point(t, 0); }); } }; const findTextNeighbour = (universe: Universe, item: E, offset: number): SpotPoint => { const stopAt = (item: E) => isBoundary(universe, item); return descendToLeft(universe, item, offset, stopAt).orThunk(() => { return descendToRight(universe, item, offset, stopAt); }).getOr(Spot.point(item, offset)); }; const repeatLeft = (universe: Universe, item: E, offset: number, process: TextSeekerPhaseProcessor): TextSeekerOutcome => { const initial = findTextNeighbour(universe, item, offset); return repeat(universe, initial.element, Gather.sidestep, Optional.some(initial.offset), process, walkLeft, Optional.none()); }; const repeatRight = (universe: Universe, item: E, offset: number, process: TextSeekerPhaseProcessor): TextSeekerOutcome => { const initial = findTextNeighbour(universe, item, offset); return repeat(universe, initial.element, Gather.sidestep, Optional.some(initial.offset), process, walkRight, Optional.none()); }; export const TextSeeker = { repeatLeft, repeatRight };