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

93.75% Statements 45/48
87.5% Branches 14/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 9257x 57x 57x 57x 57x 57x                             57x       3445x       3445x 3445x           157x 157x 157x                     3445x     1848x 1848x       2584x 2584x 2584x 2584x 2584x 107x 107x 107x 107x   2584x 2584x 2584x 2584x       1848x 1848x 1848x 1848x 1848x 1848x   1848x 2627x 2627x 2627x 2584x 2584x 2584x 2584x        
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} from '../slice';
import type {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
import type {Path} from '@jsonjoy.com/json-pointer';
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 extends Range implements Printable, Stateful {
  public readonly root: Block;
 
  constructor(
    public readonly txt: Peritext,
    start: Point,
    end: Point,
  ) {
    super(txt.str, start, end);
    this.root = new Block(txt, [], void 0, start, end);
  }
 
  // ------------------------------------------------------------------- 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, path: Path, marker: undefined | MarkerOverlayPoint, end: Point = this.end): Block {
    const txt = this.txt;
    const common = commonLength(path, parent.path);
    const start: Point = marker ? marker : this.start;
    while (parent.path.length > common && parent.parent) parent = parent.parent as Block;
    while (parent.path.length + 1 < path.length) {
      const block = new Block(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(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;
    }
  }
}