All files / json-crdt-extensions/peritext/block Fragment.ts

95.83% Statements 46/48
93.75% Branches 15/16
71.42% Functions 5/7
97.72% Lines 43/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 9660x 60x 60x 60x 60x 60x                           60x       3726x       3726x 3726x           333x 333x 333x                     3726x     2242x 2242x                 3549x 3549x 3549x 3549x 3549x 1205x 1205x 1205x 1205x   3549x 3549x 3549x 3549x       2242x 2242x 2242x 2242x 2242x 2242x   2242x 3598x 3598x 3598x 3549x 3549x 3549x 3549x        
import {Block} from './Block';
import {commonLength} from '../util/commonLength';
import {printTree} from 'tree-dump/lib/printTree';
import {LeafBlock} from './LeafBlock';
import {Range} from '../rga/Range';
import {CommonSliceType, type SliceTypeSteps} from '../slice';
import type {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
import type {Stateful} from '../types';
import type {Printable} from 'tree-dump/lib/types';
import type {Peritext} from '../Peritext';
import type {Point} from '../rga/Point';
import type {PeritextMlElement} from './types';
 
/**
 * A *fragment* represents a structural slice of a rich-text document. A
 * fragment can be bound to a specific range of text contents, however it
 * always constructs a tree of {@link Block}s, which represent the nested
 * structure of the text contents.
 */
export class Fragment<T = string> extends Range<T> implements Printable, Stateful {
  public readonly root: Block<T>;
 
  constructor(
    public readonly txt: Peritext<T>,
    start: Point<T>,
    end: Point<T>,
  ) {
    super(txt.str, start, end);
    this.root = new Block<T>(txt as Peritext<T>, [], void 0, start as Point<T>, end as Point<T>);
  }
 
  // ------------------------------------------------------------------- export
 
  public toJson(): PeritextMlElement {
    const node = this.root.toJson();
    node[0] = '';
    return node;
  }
 
  // ---------------------------------------------------------------- Printable
 
  public toString(tab: string = ''): string {
    return 'Fragment' + printTree(tab, [(tab) => this.root.toString(tab)]);
  }
 
  // ----------------------------------------------------------------- Stateful
 
  public hash: number = 0;
 
  public refresh(): number {
    this.build();
    return (this.hash = this.root.refresh());
  }
 
  private insertBlock(
    parent: Block<T>,
    path: SliceTypeSteps,
    marker: undefined | MarkerOverlayPoint<T>,
    end: Point<T> = this.end,
  ): Block<T> {
    const txt = this.txt;
    const common = commonLength(path, parent.path);
    const start: Point<T> = marker ? marker : this.start;
    while (parent.path.length > common && parent.parent) parent = parent.parent as Block<T>;
    while (parent.path.length + 1 < path.length) {
      const block = new Block<T>(txt, path.slice(0, parent.path.length + 1), void 0, start, end);
      block.parent = parent;
      parent.children.push(block);
      parent = block;
    }
    const block = new LeafBlock<T>(txt, path, marker, start, end);
    block.parent = parent;
    parent.children.push(block);
    return block;
  }
 
  protected build(): void {
    const {root} = this;
    root.children = [];
    let parent = this.root;
    const txt = this.txt;
    const overlay = txt.overlay;
    const iterator = overlay.markerPairs0(this.start, this.end);
    let pair: ReturnType<typeof iterator>;
    while ((pair = iterator())) {
      const [p1, p2] = pair;
      const skipFirstVirtualBlock = !p1 && this.start.isAbsStart() && p2 && p2.viewPos() === 0;
      if (skipFirstVirtualBlock) continue;
      const type = p1 ? p1.type() : CommonSliceType.p;
      const path = type instanceof Array ? type : [type];
      const block = this.insertBlock(parent, path, p1, p2);
      if (block.parent) parent = block.parent;
    }
  }
}