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       3688x       3688x 3688x           319x 319x 319x                     3688x     2204x 2204x                 3457x 3457x 3457x 3457x 3457x 1177x 1177x 1177x 1177x   3457x 3457x 3457x 3457x       2204x 2204x 2204x 2204x 2204x 2204x   2204x 3500x 3500x 3500x 3457x 3457x 3457x 3457x        
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;
    }
  }
}