all files / model/ operationHelpers.js

9.52% Statements 6/63
0% Branches 0/37
0% Functions 0/6
9.68% Lines 6/62
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                                                                                                                                                                             
import { isArray, isEqual } from '../util'
import ObjectOperation from './ObjectOperation'
 
/*
  Transforms change A with B, as if A was done before B.
  A' and B' can be used to update two clients to get to the
  same document content.
 
     / A - B' \
  v_n          v_n+1
     \ B - A' /
*/
export function transformDocumentChange(A, B) {
  _transformInplaceBatch(A, B)
}
 
export function transformSelection(sel, a) {
  let newSel = sel.clone()
  let hasChanged = _transformSelectionInplace(newSel, a)
  if (hasChanged) {
    return newSel
  } else {
    return sel
  }
}
 
function _transformInplaceSingle(a, b) {
  for (let i = 0; i < a.ops.length; i++) {
    let a_op = a.ops[i]
    for (let j = 0; j < b.ops.length; j++) {
      let b_op = b.ops[j]
      // ATTENTION: order of arguments is important.
      // First argument is the dominant one, i.e. it is treated as if it was applied before
      ObjectOperation.transform(a_op, b_op, {inplace: true})
    }
  }
  if (a.before) {
    _transformSelectionInplace(a.before.selection, b)
  }
  if (a.after) {
    _transformSelectionInplace(a.after.selection, b)
  }
  if (b.before) {
    _transformSelectionInplace(b.before.selection, a)
  }
  if (b.after) {
    _transformSelectionInplace(b.after.selection, a)
  }
}
 
function _transformInplaceBatch(A, B) {
  if (!isArray(A)) {
    A = [A]
  }
  if (!isArray(B)) {
    B = [B]
  }
  for (let i = 0; i < A.length; i++) {
    let a = A[i]
    for (let j = 0; j < B.length; j++) {
      let b = B[j]
      _transformInplaceSingle(a,b)
    }
  }
}
 
function _transformSelectionInplace(sel, a) {
  if (!sel || (!sel.isPropertySelection() && !sel.isContainerSelection()) ) {
    return false
  }
  let ops = a.ops
  let hasChanged = false
  let isCollapsed = sel.isCollapsed()
  for(let i=0; i<ops.length; i++) {
    let op = ops[i]
    hasChanged |= _transformCoordinateInplace(sel.start, op)
    if (!isCollapsed) {
      hasChanged |= _transformCoordinateInplace(sel.end, op)
    } else {
      if (sel.isContainerSelection()) {
        sel.end.path = sel.start.path
      }
      sel.end.offset = sel.start.offset
    }
  }
  return hasChanged
}
 
function _transformCoordinateInplace(coor, op) {
  if (!isEqual(op.path, coor.path)) return false
  let hasChanged = false
  if (op.type === 'update' && op.propertyType === 'string') {
    let diff = op.diff
    let newOffset
    if (diff.isInsert() && diff.pos <= coor.offset) {
      newOffset = coor.offset + diff.str.length
      // console.log('Transforming coordinate after inserting %s chars:', diff.str.length, coor.toString(), '->', newOffset)
      coor.offset = newOffset
      hasChanged = true
    } else if (diff.isDelete() && diff.pos <= coor.offset) {
      newOffset = Math.max(diff.pos, coor.offset - diff.str.length)
      // console.log('Transforming coordinate after deleting %s chars:', diff.str.length, coor.toString(), '->', newOffset)
      coor.offset = newOffset
      hasChanged = true
    }
  }
  return hasChanged
}