summaryrefslogtreecommitdiff
path: root/src/main/otTransform.ts
blob: f1c8331feb146777eef2a8e68b3748eb17e4170e (plain)
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
110
111
112
113
114
115
116
117
118
119
120
// Copyright (c) 2026 Yuren Hao
// Licensed under AGPL-3.0 - see LICENSE file

// OT transform functions for main process (mirror of renderer transform)
import type { OtOp } from './otTypes'
import { isInsert, isDelete, isComment } from './otTypes'

export function transformOps(
  ops1: OtOp[],
  ops2: OtOp[]
): { left: OtOp[]; right: OtOp[] } {
  let right = ops2

  const newLeft: OtOp[] = []
  for (const op1 of ops1) {
    let transformed = op1
    const newRight: OtOp[] = []
    for (const op2 of right) {
      const { left: tl, right: tr } = transformOp(transformed, op2)
      transformed = tl
      newRight.push(tr)
    }
    newLeft.push(transformed)
    right = newRight
  }

  return { left: newLeft, right }
}

function transformOp(op1: OtOp, op2: OtOp): { left: OtOp; right: OtOp } {
  if (isInsert(op1) && isInsert(op2)) {
    if (op1.p <= op2.p) {
      return { left: op1, right: { ...op2, p: op2.p + op1.i.length } }
    } else {
      return { left: { ...op1, p: op1.p + op2.i.length }, right: op2 }
    }
  }

  if (isInsert(op1) && isDelete(op2)) {
    if (op1.p <= op2.p) {
      return { left: op1, right: { ...op2, p: op2.p + op1.i.length } }
    } else if (op1.p >= op2.p + op2.d.length) {
      return { left: { ...op1, p: op1.p - op2.d.length }, right: op2 }
    } else {
      return { left: { ...op1, p: op2.p }, right: op2 }
    }
  }

  if (isDelete(op1) && isInsert(op2)) {
    if (op2.p <= op1.p) {
      return { left: { ...op1, p: op1.p + op2.i.length }, right: op2 }
    } else if (op2.p >= op1.p + op1.d.length) {
      return { left: op1, right: { ...op2, p: op2.p - op1.d.length } }
    } else {
      return { left: op1, right: { ...op2, p: op2.p - op1.d.length } }
    }
  }

  if (isDelete(op1) && isDelete(op2)) {
    if (op1.p >= op2.p + op2.d.length) {
      return {
        left: { ...op1, p: op1.p - op2.d.length },
        right: { ...op2, p: op2.p }
      }
    } else if (op2.p >= op1.p + op1.d.length) {
      return {
        left: op1,
        right: { ...op2, p: op2.p - op1.d.length }
      }
    } else {
      const overlapStart = Math.max(0, op2.p - op1.p)
      const overlapEnd = Math.min(op1.d.length, op2.p + op2.d.length - op1.p)
      let newOp1Text = op1.d
      if (overlapEnd > overlapStart) {
        newOp1Text = op1.d.slice(0, overlapStart) + op1.d.slice(overlapEnd)
      }

      const overlapStart2 = Math.max(0, op1.p - op2.p)
      const overlapEnd2 = Math.min(op2.d.length, op1.p + op1.d.length - op2.p)
      let newOp2Text = op2.d
      if (overlapEnd2 > overlapStart2) {
        newOp2Text = op2.d.slice(0, overlapStart2) + op2.d.slice(overlapEnd2)
      }

      const newP1 = op1.p <= op2.p ? op1.p : op1.p - (overlapEnd2 - overlapStart2)
      const newP2 = op2.p <= op1.p ? op2.p : op2.p - (overlapEnd - overlapStart)

      return {
        left: newOp1Text ? { d: newOp1Text, p: Math.max(0, newP1) } : { d: '', p: 0 },
        right: newOp2Text ? { d: newOp2Text, p: Math.max(0, newP2) } : { d: '', p: 0 }
      }
    }
  }

  if (isComment(op1) || isComment(op2)) {
    if (isComment(op1)) {
      if (isInsert(op2) && op2.p <= op1.p) {
        return { left: { ...op1, p: op1.p + op2.i.length }, right: op2 }
      }
      if (isDelete(op2) && op2.p < op1.p) {
        const shift = Math.min(op2.d.length, op1.p - op2.p)
        return { left: { ...op1, p: op1.p - shift }, right: op2 }
      }
    }

    if (isComment(op2)) {
      if (isInsert(op1) && op1.p <= op2.p) {
        return { left: op1, right: { ...op2, p: op2.p + op1.i.length } }
      }
      if (isDelete(op1) && op1.p < op2.p) {
        const shift = Math.min(op1.d.length, op2.p - op1.p)
        return { left: op1, right: { ...op2, p: op2.p - shift } }
      }
    }

    return { left: op1, right: op2 }
  }

  return { left: op1, right: op2 }
}