| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- /* eslint-disable
- no-cond-assign,
- no-return-assign,
- no-undef,
- no-unused-vars,
- */
- // TODO: This file was created by bulk-decaffeinate.
- // Fix any style issues and re-enable lint.
- /*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS205: Consider reworking code to avoid use of IIFEs
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
- // An alternate composable implementation for text. This is much closer
- // to the implementation used by google wave.
- //
- // Ops are lists of components which iterate over the whole document.
- // Components are either:
- // A number N: Skip N characters in the original document
- // {i:'str'}: Insert 'str' at the current position in the document
- // {d:'str'}: Delete 'str', which appears at the current position in the document
- //
- // Eg: [3, {i:'hi'}, 5, {d:'internet'}]
- //
- // Snapshots are strings.
- let makeAppend
- const p = function () {} // require('util').debug
- const i = function () {} // require('util').inspect
- const exports = typeof WEB !== 'undefined' && WEB !== null ? {} : module.exports
- exports.name = 'text-composable'
- exports.create = () => ''
- // -------- Utility methods
- const checkOp = function (op) {
- if (!Array.isArray(op)) {
- throw new Error('Op must be an array of components')
- }
- let last = null
- return (() => {
- const result = []
- for (const c of Array.from(op)) {
- if (typeof c === 'object') {
- if (
- (c.i == null || !(c.i.length > 0)) &&
- (c.d == null || !(c.d.length > 0))
- ) {
- throw new Error(`Invalid op component: ${i(c)}`)
- }
- } else {
- if (typeof c !== 'number') {
- throw new Error('Op components must be objects or numbers')
- }
- if (!(c > 0)) {
- throw new Error('Skip components must be a positive number')
- }
- if (typeof last === 'number') {
- throw new Error('Adjacent skip components should be added')
- }
- }
- result.push((last = c))
- }
- return result
- })()
- }
- // Makes a function for appending components to a given op.
- // Exported for the randomOpGenerator.
- exports._makeAppend = makeAppend = op =>
- function (component) {
- if (component === 0 || component.i === '' || component.d === '') {
- return
- }
- if (op.length === 0) {
- return op.push(component)
- } else if (
- typeof component === 'number' &&
- typeof op[op.length - 1] === 'number'
- ) {
- return (op[op.length - 1] += component)
- } else if (component.i != null && op[op.length - 1].i != null) {
- return (op[op.length - 1].i += component.i)
- } else if (component.d != null && op[op.length - 1].d != null) {
- return (op[op.length - 1].d += component.d)
- } else {
- return op.push(component)
- }
- }
- // checkOp op
- // Makes 2 functions for taking components from the start of an op, and for peeking
- // at the next op that could be taken.
- const makeTake = function (op) {
- // The index of the next component to take
- let idx = 0
- // The offset into the component
- let offset = 0
- // Take up to length n from the front of op. If n is null, take the next
- // op component. If indivisableField == 'd', delete components won't be separated.
- // If indivisableField == 'i', insert components won't be separated.
- const take = function (n, indivisableField) {
- let c
- if (idx === op.length) {
- return null
- }
- // assert.notStrictEqual op.length, i, 'The op is too short to traverse the document'
- if (typeof op[idx] === 'number') {
- if (n == null || op[idx] - offset <= n) {
- c = op[idx] - offset
- ++idx
- offset = 0
- return c
- } else {
- offset += n
- return n
- }
- } else {
- // Take from the string
- const field = op[idx].i ? 'i' : 'd'
- c = {}
- if (
- n == null ||
- op[idx][field].length - offset <= n ||
- field === indivisableField
- ) {
- c[field] = op[idx][field].slice(offset)
- ++idx
- offset = 0
- } else {
- c[field] = op[idx][field].slice(offset, offset + n)
- offset += n
- }
- return c
- }
- }
- const peekType = () => op[idx]
- return [take, peekType]
- }
- // Find and return the length of an op component
- const componentLength = function (component) {
- if (typeof component === 'number') {
- return component
- } else if (component.i != null) {
- return component.i.length
- } else {
- return component.d.length
- }
- }
- // Normalize an op, removing all empty skips and empty inserts / deletes. Concatenate
- // adjacent inserts and deletes.
- exports.normalize = function (op) {
- const newOp = []
- const append = makeAppend(newOp)
- for (const component of Array.from(op)) {
- append(component)
- }
- return newOp
- }
- // Apply the op to the string. Returns the new string.
- exports.apply = function (str, op) {
- p(`Applying ${i(op)} to '${str}'`)
- if (typeof str !== 'string') {
- throw new Error('Snapshot should be a string')
- }
- checkOp(op)
- const pos = 0
- const newDoc = []
- for (const component of Array.from(op)) {
- if (typeof component === 'number') {
- if (component > str.length) {
- throw new Error('The op is too long for this document')
- }
- newDoc.push(str.slice(0, component))
- str = str.slice(component)
- } else if (component.i != null) {
- newDoc.push(component.i)
- } else {
- if (component.d !== str.slice(0, component.d.length)) {
- throw new Error(
- `The deleted text '${
- component.d
- }' doesn't match the next characters in the document '${str.slice(
- 0,
- component.d.length
- )}'`
- )
- }
- str = str.slice(component.d.length)
- }
- }
- if (str !== '') {
- throw new Error("The applied op doesn't traverse the entire document")
- }
- return newDoc.join('')
- }
- // transform op1 by op2. Return transformed version of op1.
- // op1 and op2 are unchanged by transform.
- exports.transform = function (op, otherOp, side) {
- let component
- if (side !== 'left' && side !== 'right') {
- throw new Error(`side (${side} must be 'left' or 'right'`)
- }
- checkOp(op)
- checkOp(otherOp)
- const newOp = []
- const append = makeAppend(newOp)
- const [take, peek] = Array.from(makeTake(op))
- for (component of Array.from(otherOp)) {
- let chunk, length
- if (typeof component === 'number') {
- // Skip
- length = component
- while (length > 0) {
- chunk = take(length, 'i')
- if (chunk === null) {
- throw new Error(
- 'The op traverses more elements than the document has'
- )
- }
- append(chunk)
- if (typeof chunk !== 'object' || chunk.i == null) {
- length -= componentLength(chunk)
- }
- }
- } else if (component.i != null) {
- // Insert
- if (side === 'left') {
- // The left insert should go first.
- const o = peek()
- if (o != null ? o.i : undefined) {
- append(take())
- }
- }
- // Otherwise, skip the inserted text.
- append(component.i.length)
- } else {
- // Delete.
- // assert.ok component.d
- ;({ length } = component.d)
- while (length > 0) {
- chunk = take(length, 'i')
- if (chunk === null) {
- throw new Error(
- 'The op traverses more elements than the document has'
- )
- }
- if (typeof chunk === 'number') {
- length -= chunk
- } else if (chunk.i != null) {
- append(chunk)
- } else {
- // assert.ok chunk.d
- // The delete is unnecessary now.
- length -= chunk.d.length
- }
- }
- }
- }
- // Append extras from op1
- while ((component = take())) {
- if ((component != null ? component.i : undefined) == null) {
- throw new Error(`Remaining fragments in the op: ${i(component)}`)
- }
- append(component)
- }
- return newOp
- }
- // Compose 2 ops into 1 op.
- exports.compose = function (op1, op2) {
- let component
- p(`COMPOSE ${i(op1)} + ${i(op2)}`)
- checkOp(op1)
- checkOp(op2)
- const result = []
- const append = makeAppend(result)
- const [take, _] = Array.from(makeTake(op1))
- for (component of Array.from(op2)) {
- let chunk, length
- if (typeof component === 'number') {
- // Skip
- length = component
- while (length > 0) {
- chunk = take(length, 'd')
- if (chunk === null) {
- throw new Error(
- 'The op traverses more elements than the document has'
- )
- }
- append(chunk)
- if (typeof chunk !== 'object' || chunk.d == null) {
- length -= componentLength(chunk)
- }
- }
- } else if (component.i != null) {
- // Insert
- append({ i: component.i })
- } else {
- // Delete
- let offset = 0
- while (offset < component.d.length) {
- chunk = take(component.d.length - offset, 'd')
- if (chunk === null) {
- throw new Error(
- 'The op traverses more elements than the document has'
- )
- }
- // If its delete, append it. If its skip, drop it and decrease length. If its insert, check the strings match, drop it and decrease length.
- if (typeof chunk === 'number') {
- append({ d: component.d.slice(offset, offset + chunk) })
- offset += chunk
- } else if (chunk.i != null) {
- if (component.d.slice(offset, offset + chunk.i.length) !== chunk.i) {
- throw new Error("The deleted text doesn't match the inserted text")
- }
- offset += chunk.i.length
- // The ops cancel each other out.
- } else {
- // Delete
- append(chunk)
- }
- }
- }
- }
- // Append extras from op1
- while ((component = take())) {
- if ((component != null ? component.d : undefined) == null) {
- throw new Error(`Trailing stuff in op1 ${i(component)}`)
- }
- append(component)
- }
- return result
- }
- const invertComponent = function (c) {
- if (typeof c === 'number') {
- return c
- } else if (c.i != null) {
- return { d: c.i }
- } else {
- return { i: c.d }
- }
- }
- // Invert an op
- exports.invert = function (op) {
- const result = []
- const append = makeAppend(result)
- for (const component of Array.from(op)) {
- append(invertComponent(component))
- }
- return result
- }
- if (typeof window !== 'undefined' && window !== null) {
- if (!window.ot) {
- window.ot = {}
- }
- if (!window.ot.types) {
- window.ot.types = {}
- }
- window.ot.types.text = exports
- }
|