DiffGenerator.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /* eslint-disable
  2. camelcase,
  3. no-proto,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let DiffGenerator
  16. function ConsistencyError(message) {
  17. const error = new Error(message)
  18. error.name = 'ConsistencyError'
  19. error.__proto__ = ConsistencyError.prototype
  20. return error
  21. }
  22. ConsistencyError.prototype.__proto__ = Error.prototype
  23. const logger = require('@overleaf/logger')
  24. module.exports = DiffGenerator = {
  25. ConsistencyError,
  26. rewindUpdate(content, update) {
  27. for (let j = update.op.length - 1, i = j; j >= 0; j--, i = j) {
  28. const op = update.op[i]
  29. if (op.broken !== true) {
  30. try {
  31. content = DiffGenerator.rewindOp(content, op)
  32. } catch (e) {
  33. if (e instanceof ConsistencyError && (i = update.op.length - 1)) {
  34. // catch known case where the last op in an array has been
  35. // merged into a later op
  36. logger.warn(
  37. { err: e, update, op: JSON.stringify(op) },
  38. 'marking op as broken'
  39. )
  40. op.broken = true
  41. } else {
  42. throw e // rethrow the execption
  43. }
  44. }
  45. }
  46. }
  47. return content
  48. },
  49. rewindOp(content, op) {
  50. let p
  51. if (op.i != null) {
  52. // ShareJS will accept an op where p > content.length when applied,
  53. // and it applies as though p == content.length. However, the op is
  54. // passed to us with the original p > content.length. Detect if that
  55. // is the case with this op, and shift p back appropriately to match
  56. // ShareJS if so.
  57. ;({ p } = op)
  58. const max_p = content.length - op.i.length
  59. if (p > max_p) {
  60. logger.warn({ max_p, p }, 'truncating position to content length')
  61. p = max_p
  62. op.p = p // fix out of range offsets to avoid invalid history exports in ZipManager
  63. }
  64. const textToBeRemoved = content.slice(p, p + op.i.length)
  65. if (op.i !== textToBeRemoved) {
  66. throw new ConsistencyError(
  67. `Inserted content, '${op.i}', does not match text to be removed, '${textToBeRemoved}'`
  68. )
  69. }
  70. return content.slice(0, p) + content.slice(p + op.i.length)
  71. } else if (op.d != null) {
  72. if (op.p > content.length) {
  73. op.p = content.length // fix out of range offsets to avoid invalid history exports in ZipManager
  74. }
  75. return content.slice(0, op.p) + op.d + content.slice(op.p)
  76. } else {
  77. return content
  78. }
  79. },
  80. rewindUpdates(content, updates) {
  81. for (const update of Array.from(updates.reverse())) {
  82. try {
  83. content = DiffGenerator.rewindUpdate(content, update)
  84. } catch (e) {
  85. e.attempted_update = update // keep a record of the attempted update
  86. throw e // rethrow the exception
  87. }
  88. }
  89. return content
  90. },
  91. buildDiff(initialContent, updates) {
  92. let diff = [{ u: initialContent }]
  93. for (const update of Array.from(updates)) {
  94. diff = DiffGenerator.applyUpdateToDiff(diff, update)
  95. }
  96. diff = DiffGenerator.compressDiff(diff)
  97. return diff
  98. },
  99. compressDiff(diff) {
  100. const newDiff = []
  101. for (const part of Array.from(diff)) {
  102. const lastPart = newDiff[newDiff.length - 1]
  103. if (
  104. lastPart != null &&
  105. (lastPart.meta != null ? lastPart.meta.user : undefined) != null &&
  106. (part.meta != null ? part.meta.user : undefined) != null
  107. ) {
  108. if (
  109. lastPart.i != null &&
  110. part.i != null &&
  111. lastPart.meta.user.id === part.meta.user.id
  112. ) {
  113. lastPart.i += part.i
  114. lastPart.meta.start_ts = Math.min(
  115. lastPart.meta.start_ts,
  116. part.meta.start_ts
  117. )
  118. lastPart.meta.end_ts = Math.max(
  119. lastPart.meta.end_ts,
  120. part.meta.end_ts
  121. )
  122. } else if (
  123. lastPart.d != null &&
  124. part.d != null &&
  125. lastPart.meta.user.id === part.meta.user.id
  126. ) {
  127. lastPart.d += part.d
  128. lastPart.meta.start_ts = Math.min(
  129. lastPart.meta.start_ts,
  130. part.meta.start_ts
  131. )
  132. lastPart.meta.end_ts = Math.max(
  133. lastPart.meta.end_ts,
  134. part.meta.end_ts
  135. )
  136. } else {
  137. newDiff.push(part)
  138. }
  139. } else {
  140. newDiff.push(part)
  141. }
  142. }
  143. return newDiff
  144. },
  145. applyOpToDiff(diff, op, meta) {
  146. let consumedDiff
  147. const position = 0
  148. let remainingDiff = diff.slice()
  149. ;({ consumedDiff, remainingDiff } = DiffGenerator._consumeToOffset(
  150. remainingDiff,
  151. op.p
  152. ))
  153. const newDiff = consumedDiff
  154. if (op.i != null) {
  155. newDiff.push({
  156. i: op.i,
  157. meta,
  158. })
  159. } else if (op.d != null) {
  160. ;({ consumedDiff, remainingDiff } =
  161. DiffGenerator._consumeDiffAffectedByDeleteOp(remainingDiff, op, meta))
  162. newDiff.push(...Array.from(consumedDiff || []))
  163. }
  164. newDiff.push(...Array.from(remainingDiff || []))
  165. return newDiff
  166. },
  167. applyUpdateToDiff(diff, update) {
  168. for (const op of Array.from(update.op)) {
  169. if (op.broken !== true) {
  170. diff = DiffGenerator.applyOpToDiff(diff, op, update.meta)
  171. }
  172. }
  173. return diff
  174. },
  175. _consumeToOffset(remainingDiff, totalOffset) {
  176. let part
  177. const consumedDiff = []
  178. let position = 0
  179. while ((part = remainingDiff.shift())) {
  180. const length = DiffGenerator._getLengthOfDiffPart(part)
  181. if (part.d != null) {
  182. consumedDiff.push(part)
  183. } else if (position + length >= totalOffset) {
  184. const partOffset = totalOffset - position
  185. if (partOffset > 0) {
  186. consumedDiff.push(DiffGenerator._slicePart(part, 0, partOffset))
  187. }
  188. if (partOffset < length) {
  189. remainingDiff.unshift(DiffGenerator._slicePart(part, partOffset))
  190. }
  191. break
  192. } else {
  193. position += length
  194. consumedDiff.push(part)
  195. }
  196. }
  197. return {
  198. consumedDiff,
  199. remainingDiff,
  200. }
  201. },
  202. _consumeDiffAffectedByDeleteOp(remainingDiff, deleteOp, meta) {
  203. const consumedDiff = []
  204. let remainingOp = deleteOp
  205. while (remainingOp && remainingDiff.length > 0) {
  206. let newPart
  207. ;({ newPart, remainingDiff, remainingOp } =
  208. DiffGenerator._consumeDeletedPart(remainingDiff, remainingOp, meta))
  209. if (newPart != null) {
  210. consumedDiff.push(newPart)
  211. }
  212. }
  213. return {
  214. consumedDiff,
  215. remainingDiff,
  216. }
  217. },
  218. _consumeDeletedPart(remainingDiff, op, meta) {
  219. let deletedContent, newPart, remainingOp
  220. const part = remainingDiff.shift()
  221. const partLength = DiffGenerator._getLengthOfDiffPart(part)
  222. if (part.d != null) {
  223. // Skip existing deletes
  224. remainingOp = op
  225. newPart = part
  226. } else if (partLength > op.d.length) {
  227. // Only the first bit of the part has been deleted
  228. const remainingPart = DiffGenerator._slicePart(part, op.d.length)
  229. remainingDiff.unshift(remainingPart)
  230. deletedContent = DiffGenerator._getContentOfPart(part).slice(
  231. 0,
  232. op.d.length
  233. )
  234. if (deletedContent !== op.d) {
  235. throw new ConsistencyError(
  236. `deleted content, '${deletedContent}', does not match delete op, '${op.d}'`
  237. )
  238. }
  239. if (part.u != null) {
  240. newPart = {
  241. d: op.d,
  242. meta,
  243. }
  244. } else if (part.i != null) {
  245. newPart = null
  246. }
  247. remainingOp = null
  248. } else if (partLength === op.d.length) {
  249. // The entire part has been deleted, but it is the last part
  250. deletedContent = DiffGenerator._getContentOfPart(part)
  251. if (deletedContent !== op.d) {
  252. throw new ConsistencyError(
  253. `deleted content, '${deletedContent}', does not match delete op, '${op.d}'`
  254. )
  255. }
  256. if (part.u != null) {
  257. newPart = {
  258. d: op.d,
  259. meta,
  260. }
  261. } else if (part.i != null) {
  262. newPart = null
  263. }
  264. remainingOp = null
  265. } else if (partLength < op.d.length) {
  266. // The entire part has been deleted and there is more
  267. deletedContent = DiffGenerator._getContentOfPart(part)
  268. const opContent = op.d.slice(0, deletedContent.length)
  269. if (deletedContent !== opContent) {
  270. throw new ConsistencyError(
  271. `deleted content, '${deletedContent}', does not match delete op, '${opContent}'`
  272. )
  273. }
  274. if (part.u) {
  275. newPart = {
  276. d: part.u,
  277. meta,
  278. }
  279. } else if (part.i != null) {
  280. newPart = null
  281. }
  282. remainingOp = {
  283. p: op.p,
  284. d: op.d.slice(DiffGenerator._getLengthOfDiffPart(part)),
  285. }
  286. }
  287. return {
  288. newPart,
  289. remainingDiff,
  290. remainingOp,
  291. }
  292. },
  293. _slicePart(basePart, from, to) {
  294. let part
  295. if (basePart.u != null) {
  296. part = { u: basePart.u.slice(from, to) }
  297. } else if (basePart.i != null) {
  298. part = { i: basePart.i.slice(from, to) }
  299. }
  300. if (basePart.meta != null) {
  301. part.meta = basePart.meta
  302. }
  303. return part
  304. },
  305. _getLengthOfDiffPart(part) {
  306. return (part.u || part.d || part.i || '').length
  307. },
  308. _getContentOfPart(part) {
  309. return part.u || part.d || part.i || ''
  310. },
  311. }