All files / json-ot/types/ot-string compose.ts

100% Statements 61/61
100% Branches 28/28
100% Functions 1/1
100% Lines 54/54

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  3x                             3x 1011x 1011x 1011x 1011x 1011x 1011x 3283x 3283x 3283x   1447x 768x 768x 2053x 2053x 2053x 2053x 2053x 2053x 2053x 2053x 1009x 1009x   2053x   679x 1447x     1116x 1116x     720x 720x     3283x 1399x 1399x 1399x 1399x 3894x 3894x 3894x 3894x 3894x 3894x 3894x 2126x 1205x 3894x 3894x 2014x 2014x   3894x       1011x 1221x 1011x 1011x    
import type {StringOp} from './types';
import {append, chunk, componentLength, isDeleteComponent, trim} from './util';
 
/**
 * Combine two operations into one, such that the changes produced by the
 * by the single operation are the same as if the two operations were applied
 * in sequence.
 *
 * ```
 * apply(str, combine(op1, op2)) === apply(apply(str, op1), op2)
 * ```
 *
 * @param op1 First operation.
 * @param op2 Second operation.
 * @returns A combined operation.
 */
export const compose = (op1: StringOp, op2: StringOp): StringOp => {
  const op3: StringOp = [];
  const len1 = op1.length;
  const len2 = op2.length;
  let off1 = 0;
  let i1 = 0;
  for (let i2 = 0; i2 < len2; i2++) {
    const comp2 = op2[i2];
    let doDelete = false;
    switch (typeof comp2) {
      case 'number': {
        if (comp2 > 0) {
          let length2 = comp2;
          while (length2 > 0) {
            const comp1 = op1[i1];
            const comp = i1 >= len1 ? length2 : chunk(comp1, off1, length2);
            const compLength = componentLength(comp);
            const isDelete = isDeleteComponent(comp);
            const length1 = componentLength(comp1 || comp);
            append(op3, comp);
            off1 += compLength;
            if (off1 >= length1) {
              i1++;
              off1 = 0;
            }
            if (!isDelete) length2 -= compLength;
          }
        } else doDelete = true;
        break;
      }
      case 'string': {
        append(op3, comp2);
        break;
      }
      case 'object': {
        doDelete = true;
        break;
      }
    }
    if (doDelete) {
      const isReversible = comp2 instanceof Array;
      const length2 = isReversible ? comp2[0].length : -comp2;
      let off2 = 0;
      while (off2 < length2) {
        const remaining = length2 - off2;
        const comp1 = op1[i1];
        const comp = i1 >= len1 ? remaining : chunk(comp1, off1, remaining);
        const compLength = componentLength(comp);
        const isDelete = isDeleteComponent(comp);
        const length1 = componentLength(comp1 || comp);
        if (isDelete) append(op3, comp);
        else if (typeof comp === 'number')
          append(op3, isReversible ? [comp2[0].substring(off2, off2 + compLength)] : -compLength);
        off1 += compLength;
        if (off1 >= length1) {
          i1++;
          off1 = 0;
        }
        if (!isDelete) off2 += compLength;
      }
    }
  }
  if (i1 < len1 && off1) append(op3, chunk(op1[i1++], off1, Number.POSITIVE_INFINITY));
  for (; i1 < len1; i1++) append(op3, op1[i1]);
  trim(op3);
  return op3;
};