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 3326x 3326x 3326x   1491x 738x 738x 2029x 2029x 2029x 2029x 2029x 2029x 2029x 2029x 887x 887x   2029x   753x 1491x     1135x 1135x     700x 700x     3326x 1453x 1453x 1453x 1453x 3949x 3949x 3949x 3949x 3949x 3949x 3949x 2147x 1191x 3949x 3949x 2002x 2002x   3949x       1011x 1236x 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;
};