diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 812a9115..8465a5d7 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -1,17 +1,5 @@ 'use strict'; -/* - * This is the Changeset library copied from the old Etherpad with some modifications - * to use it in node.js - * Can be found in https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js - */ - -/** - * This code is mostly from the old Etherpad. Please help us to comment this code. - * This helps other people to understand this code better and helps them to improve it. - * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED - */ - /* * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * @@ -28,15 +16,18 @@ * limitations under the License. */ +/* + * This is the Changeset library copied from the old Etherpad with some modifications + * to use it in node.js. The original can be found at: + * https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js + */ + const AttributePool = require('./AttributePool'); /** - * ==================== General Util Functions ======================= - */ - -/** - * This method is called whenever there is an error in the sync process - * @param msg {string} Just some message + * This method is called whenever there is an error in the sync process. + * + * @param {string} msg - Just some message */ exports.error = (msg) => { const e = new Error(msg); @@ -45,10 +36,11 @@ exports.error = (msg) => { }; /** - * This method is used for assertions with Messages - * if assert fails, the error function is called. - * @param b {boolean} assertion condition - * @param msgParts {string} error to be passed if it fails + * Assert that a condition is truthy. If the condition is falsy, the `error` function is called to + * throw an exception. + * + * @param {boolean} b - assertion condition + * @param {...string} msgParts - error message to include in the exception */ exports.assert = (b, ...msgParts) => { if (!b) { @@ -57,41 +49,94 @@ exports.assert = (b, ...msgParts) => { }; /** - * Parses a number from string base 36 - * @param str {string} string of the number in base 36 - * @returns {int} number + * Parses a number from string base 36. + * + * @param {string} str - string of the number in base 36 + * @returns {number} number */ exports.parseNum = (str) => parseInt(str, 36); /** - * Writes a number in base 36 and puts it in a string - * @param num {int} number + * Writes a number in base 36 and puts it in a string. + * + * @param {number} num - number * @returns {string} string */ exports.numToString = (num) => num.toString(36).toLowerCase(); /** - * ==================== Changeset Functions ======================= + * An operation to apply to a shared document. + * + * @typedef {object} Op + * @property {('+'|'-'|'='|'')} opcode - The operation's operator: + * - '=': Keep the next `chars` characters (containing `lines` newlines) from the base + * document. + * - '-': Remove the next `chars` characters (containing `lines` newlines) from the base + * document. + * - '+': Insert `chars` characters (containing `lines` newlines) at the current position in + * the document. The inserted characters come from the changeset's character bank. + * - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an + * operation. + * @property {number} chars - The number of characters to keep, insert, or delete. + * @property {number} lines - The number of characters among the `chars` characters that are + * newlines. If non-zero, the last character must be a newline. + * @property {string} attribs - Identifiers of attributes to apply to the text, represented as a + * repeated (zero or more) sequence of asterisk followed by a non-negative base-36 (lower-case) + * integer. For example, '*2*1o' indicates that attributes 2 and 60 apply to the text affected + * by the operation. The identifiers come from the document's attribution pool. This is the + * empty string for remove ('-') operations. For keep ('=') operations, the attributes are + * merged with the base text's existing attributes: + * - A keep op attribute with a non-empty value replaces an existing base text attribute that + * has the same key. + * - A keep op attribute with an empty value is interpreted as an instruction to remove an + * existing base text attribute that has the same key, if one exists. */ /** - * returns the required length of the text before changeset - * can be applied - * @param cs {string} String representation of the Changeset + * Describes changes to apply to a document. Does not include the attribute pool or the original + * document. + * + * @typedef {object} Changeset + * @property {number} oldLen - The length of the base document. + * @property {number} newLen - The length of the document after applying the changeset. + * @property {string} ops - Serialized sequence of operations. Use `deserializeOps` to parse this + * string. + * @property {string} charBank - Characters inserted by insert operations. + */ + +/** + * Returns the required length of the text before changeset can be applied. + * + * @param {string} cs - String representation of the Changeset + * @returns {number} oldLen property */ exports.oldLen = (cs) => exports.unpack(cs).oldLen; /** - * returns the length of the text after changeset is applied - * @param cs {string} String representation of the Changeset + * Returns the length of the text after changeset is applied. + * + * @param {string} cs - String representation of the Changeset + * @returns {number} newLen property */ exports.newLen = (cs) => exports.unpack(cs).newLen; /** - * this function creates an iterator which decodes string changeset operations - * @param opsStr {string} String encoding of the change operations to be performed - * @param optStartIndex {int} from where in the string should the iterator start - * @return {Op} type object iterator + * Iterator over a changeset's operations. + * + * Note: This class does NOT implement the ECMAScript iterable or iterator protocols. + * + * @typedef {object} OpIter + * @property {Function} hasNext - + * @property {Function} lastIndex - + * @property {Function} next - + */ + +/** + * Creates an iterator which decodes string changeset operations. + * + * @param {string} opsStr - String encoding of the change operations to perform. + * @param {number} [optStartIndex=0] - From where in the string should the iterator start. + * @returns {OpIter} Operator iterator object. */ exports.opIterator = (opsStr, optStartIndex) => { const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; @@ -138,8 +183,9 @@ exports.opIterator = (opsStr, optStartIndex) => { }; /** - * Cleans an Op object - * @param {Op} object to be cleared + * Cleans an Op object. + * + * @param {Op} op - object to clear */ exports.clearOp = (op) => { op.opcode = ''; @@ -150,7 +196,9 @@ exports.clearOp = (op) => { /** * Creates a new Op object - * @param optOpcode the type operation of the Op object + * + * @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator. + * @returns {Op} */ exports.newOp = (optOpcode) => ({ opcode: (optOpcode || ''), @@ -161,8 +209,9 @@ exports.newOp = (optOpcode) => ({ /** * Copies op1 to op2 - * @param op1 src Op - * @param op2 dest Op + * + * @param {Op} op1 - src Op + * @param {Op} op2 - dest Op */ exports.copyOp = (op1, op2) => { op2.opcode = op1.opcode; @@ -172,12 +221,51 @@ exports.copyOp = (op1, op2) => { }; /** - * Used to check if a Changeset if valid - * @param cs {Changeset} Changeset to be checked + * Serializes a sequence of Ops. + * + * @typedef {object} OpAssembler + * @property {Function} append - + * @property {Function} clear - + * @property {Function} toString - + */ + +/** + * Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final + * pure "keeps". It does not re-order operations. + * + * @typedef {object} MergingOpAssembler + * @property {Function} append - + * @property {Function} clear - + * @property {Function} endDocument - + * @property {Function} toString - + */ + +/** + * Creates an object that allows you to append operations (type Op) and also compresses them if + * possible. Like MergingOpAssembler, but able to produce conforming exportss from slightly looser + * input, at the cost of speed. Specifically: + * - merges consecutive operations that can be merged + * - strips final "=" + * - ignores 0-length changes + * - reorders consecutive + and - (which MergingOpAssembler doesn't do) + * + * @typedef {object} SmartOpAssembler + * @property {Function} append - + * @property {Function} appendOpWithText - + * @property {Function} clear - + * @property {Function} endDocument - + * @property {Function} getLengthChange - + * @property {Function} toString - + */ + +/** + * Used to check if a Changeset is valid. This function does not check things that require access to + * the attribute pool (e.g., attribute order) or original text (e.g., newline positions). + * + * @param {string} cs - Changeset to check + * @returns {string} the checked Changeset */ exports.checkRep = (cs) => { - // doesn't check things that require access to attrib pool (e.g. attribute order) - // or original string (e.g. newline positions) const unpacked = exports.unpack(cs); const oldLen = unpacked.oldLen; const newLen = unpacked.newLen; @@ -224,23 +312,10 @@ exports.checkRep = (cs) => { return cs; }; - /** - * ==================== Util Functions ======================= - */ - -/** - * creates an object that allows you to append operations (type Op) and also - * compresses them if possible + * @returns {SmartOpAssembler} */ exports.smartOpAssembler = () => { - // Like opAssembler but able to produce conforming exportss - // from slightly looser input, at the cost of speed. - // Specifically: - // - merges consecutive operations that can be merged - // - strips final "=" - // - ignores 0-length changes - // - reorders consecutive + and - (which margingOpAssembler doesn't do) const minusAssem = exports.mergingOpAssembler(); const plusAssem = exports.mergingOpAssembler(); const keepAssem = exports.mergingOpAssembler(); @@ -333,12 +408,10 @@ exports.smartOpAssembler = () => { }; }; - +/** + * @returns {MergingOpAssembler} + */ exports.mergingOpAssembler = () => { - // This assembler can be used in production; it efficiently - // merges consecutive operations that are mergeable, ignores - // no-ops, and drops final pure "keeps". It does not re-order - // operations. const assem = exports.opAssembler(); const bufOp = exports.newOp(); @@ -408,11 +481,15 @@ exports.mergingOpAssembler = () => { }; }; - +/** + * @returns {OpAssembler} + */ exports.opAssembler = () => { const pieces = []; - // this function allows op to be mutated later (doesn't keep a ref) + /** + * @param {Op} op - Operation to add. Ownership remains with the caller. + */ const append = (op) => { pieces.push(op.attribs); if (op.lines) { @@ -436,7 +513,18 @@ exports.opAssembler = () => { /** * A custom made String Iterator - * @param str {string} String to be iterated over + * + * @typedef {object} StringIterator + * @property {Function} newlines - + * @property {Function} peek - + * @property {Function} remaining - + * @property {Function} skip - + * @property {Function} take - + */ + +/** + * @param {string} str - String to iterate over + * @returns {StringIterator} */ exports.stringIterator = (str) => { let curIndex = 0; @@ -479,10 +567,21 @@ exports.stringIterator = (str) => { /** * A custom made StringBuffer + * + * @typedef {object} StringAssembler + * @property {Function} append - + * @property {Function} toString - + */ + +/** + * @returns {StringAssembler} */ exports.stringAssembler = () => { const pieces = []; + /** + * @param {string} x - + */ const append = (x) => { pieces.push(String(x)); }; @@ -495,25 +594,48 @@ exports.stringAssembler = () => { }; /** - * This class allows to iterate and modify texts which have several lines - * It is used for applying Changesets on arrays of lines - * Note from prev docs: "lines" need not be an array as long as it supports - * certain calls (lines_foo inside). + * Class to iterate and modify texts which have several lines. It is used for applying Changesets on + * arrays of lines. + * + * Mutation operations have the same constraints as exports operations with respect to newlines, but + * not the other additional constraints (i.e. ins/del ordering, forbidden no-ops, non-mergeability, + * final newline). Can be used to mutate lists of strings where the last char of each string is not + * actually a newline, but for the purposes of N and L values, the caller should pretend it is, and + * for things to work right in that case, the input to the `insert` method should be a single line + * with no newlines. + * + * @typedef {object} TextLinesMutator + * @property {Function} close - + * @property {Function} hasMore - + * @property {Function} insert - + * @property {Function} remove - + * @property {Function} removeLines - + * @property {Function} skip - + * @property {Function} skipLines - + */ + +/** + * @param {string[]} lines - Lines to mutate (in place). This does not need to be an array as long + * as it supports certain methods/properties: + * - `get(i)`: Returns the line at index `i`. + * - `length`: Number like `Array.prototype.length`, or a method that returns the length. + * - `slice(...)`: Like `Array.prototype.slice(...)`. Optional if the return value of the + * `removeLines` method is not needed. + * - `splice(...)`: Like `Array.prototype.splice(...)`. + * @returns {TextLinesMutator} */ exports.textLinesMutator = (lines) => { - // Mutates lines, an array of strings, in place. - // Mutation operations have the same constraints as exports operations - // with respect to newlines, but not the other additional constraints - // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline). - // Can be used to mutate lists of strings where the last char of each string - // is not actually a newline, but for the purposes of N and L values, - // the caller should pretend it is, and for things to work right in that case, the input - // to insert() should be a single line with no newlines. - - // The splice holds information which lines are to be deleted or changed. - // curSplice[0] is an index into the lines array - // curSplice[1] is the number of lines that will be removed from lines - // the other elements represent mutated (changed by ops) lines or new lines (added by ops) + /** + * curSplice holds values that will be passed as arguments to lines.splice() to insert, delete, or + * change lines: + * - curSplice[0] is an index into the lines array. + * - curSplice[1] is the number of lines that will be removed from the lines array starting at + * the index. + * - The other elements represent mutated (changed by ops) lines or new lines (added by ops) to + * insert at the index. + * + * @type {[number, number?, ...string[]?]} + */ const curSplice = [0, 0]; let inSplice = false; @@ -526,17 +648,19 @@ exports.textLinesMutator = (lines) => { // curCol == 0 /** - * Adds and/or removes entries at a specific offset in lines array - * It is called when leaving the splice - * @param {Array} s curSplice + * Adds and/or removes entries at a specific offset in `lines`. Called when leaving the splice. + * + * @param {[number, number?, ...string[]?]} s - curSplice */ const linesApplySplice = (s) => { lines.splice(...s); }; /** - * Get a line from lines at given index - * @param {Number} idx an index + * Get a line from `lines` at given index. + * + * @param {number} idx - an index + * @returns {string} */ const linesGet = (idx) => { if (lines.get) { @@ -545,12 +669,13 @@ exports.textLinesMutator = (lines) => { return lines[idx]; } }; - // can be unimplemented if removeLines's return value not needed /** - * Return a slice from lines array - * @param {Number} start the start index - * @param {Number} end the end index + * Return a slice from `lines`. + * + * @param {number} start - the start index + * @param {number} end - the end index + * @returns {string[]} */ const linesSlice = (start, end) => { if (lines.slice) { @@ -561,7 +686,9 @@ exports.textLinesMutator = (lines) => { }; /** - * Return the length of lines array + * Return the length of `lines`. + * + * @returns {number} */ const linesLength = () => { if ((typeof lines.length) === 'number') { @@ -586,9 +713,8 @@ exports.textLinesMutator = (lines) => { }; /** - * Changes the lines array according to the values in curSplice - * and resets curSplice. - * This is called via close or TODO(doc) + * Changes the lines array according to the values in curSplice and resets curSplice. Called via + * close or TODO(doc). */ const leaveSplice = () => { linesApplySplice(curSplice); @@ -599,18 +725,18 @@ exports.textLinesMutator = (lines) => { /** * Indicates if curLine is already in the splice. This is necessary because the last element in - * curSplice is curLine when this line is currently worked on (e.g. when skipping are inserting) + * curSplice is curLine when this line is currently worked on (e.g. when skipping are inserting). * * TODO(doc) why aren't removals considered? - * @returns {Boolean} true if curLine is in splice + * + * @returns {boolean} true if curLine is in splice */ const isCurLineInSplice = () => (curLine - curSplice[0] < (curSplice.length - 2)); /** - * Incorporates current line into the splice - * and marks its old position to be deleted. + * Incorporates current line into the splice and marks its old position to be deleted. * - * @returns {Number} the index of the added line in curSplice + * @returns {number} the index of the added line in curSplice */ const putCurLineInSplice = () => { if (!isCurLineInSplice()) { @@ -623,7 +749,8 @@ exports.textLinesMutator = (lines) => { /** * It will skip some newlines by putting them into the splice. * - * @param {Boolean} includeInSplice indicates if attributes are present + * @param {number} L - + * @param {boolean} includeInSplice - indicates if attributes are present */ const skipLines = (L, includeInSplice) => { if (L) { @@ -656,9 +783,9 @@ exports.textLinesMutator = (lines) => { /** * Skip some characters. Can contain newlines. * - * @param {Number} N number of characters to skip - * @param {Number} L number of newlines to skip - * @param {Boolean} includeInSplice indicates if attributes are present + * @param {number} N - number of characters to skip + * @param {number} L - number of newlines to skip + * @param {boolean} includeInSplice - indicates if attributes are present */ const skip = (N, L, includeInSplice) => { if (N) { @@ -679,9 +806,10 @@ exports.textLinesMutator = (lines) => { }; /** - * Remove whole lines from lines array + * Remove whole lines from lines array. * - * @param {Number} L number of lines to be removed + * @param {number} L - number of lines to remove + * @returns {string} */ const removeLines = (L) => { let removed = ''; @@ -691,10 +819,10 @@ exports.textLinesMutator = (lines) => { } /** - * Gets a string of joined lines after the end of the splice + * Gets a string of joined lines after the end of the splice. * - * @param k {Number} number of lines - * @returns {String} joined lines + * @param {number} k - number of lines + * @returns {string} joined lines */ const nextKLinesText = (k) => { const m = curSplice[0] + curSplice[1]; @@ -724,10 +852,11 @@ exports.textLinesMutator = (lines) => { }; /** - * Remove text from lines array + * Remove text from lines array. * - * @param N {Number} characters to delete - * @param L {Number} lines to delete + * @param {number} N - characters to delete + * @param {number} L - lines to delete + * @returns {string} */ const remove = (N, L) => { let removed = ''; @@ -752,8 +881,8 @@ exports.textLinesMutator = (lines) => { /** * Inserts text into lines array. * - * @param text {String} the text to insert - * @param L {Number} number of newlines in text + * @param {string} text - the text to insert + * @param {number} L - number of newlines in text */ const insert = (text, L) => { if (text) { @@ -764,6 +893,7 @@ exports.textLinesMutator = (lines) => { const newLines = exports.splitTextLines(text); if (isCurLineInSplice()) { const sline = curSplice.length - 1; + /** @type {string} */ const theLine = curSplice[sline]; const lineCol = curCol; // insert the first new line @@ -797,10 +927,9 @@ exports.textLinesMutator = (lines) => { }; /** - * Checks if curLine (the line we are in when curSplice is applied) is the last line - * in lines. + * Checks if curLine (the line we are in when curSplice is applied) is the last line in `lines`. * - * @return {Boolean} indicates if there are lines left + * @returns {boolean} indicates if there are lines left */ const hasMore = () => { let docLines = linesLength(); @@ -832,19 +961,27 @@ exports.textLinesMutator = (lines) => { }; /** - * Function allowing iterating over two Op strings. - * @params in1 {string} first Op string - * @params idx1 {int} integer where 1st iterator should start - * @params in2 {string} second Op string - * @params idx2 {int} integer where 2nd iterator should start - * @params func {function} which decides how 1st or 2nd iterator - * advances. When opX.opcode = 0, iterator X advances to - * next element - * func has signature f(op1, op2, opOut) - * op1 - current operation of the first iterator - * op2 - current operation of the second iterator - * opOut - result operator to be put into Changeset - * @return {string} the integrated changeset + * Apply operations to other operations. + * + * @param {string} in1 - first Op string + * @param {number} idx1 - integer where 1st iterator should start + * @param {string} in2 - second Op string + * @param {number} idx2 - integer where 2nd iterator should start + * @param {Function} func - Callback that applies an operation to another operation. Will be called + * multiple times depending on the number of operations in `in1` and `in2`. `func` has signature + * `f(op1, op2, opOut)`: + * - `op1` is the current operation from `in1`. `func` is expected to mutate `op1` to + * partially or fully consume it, and MUST set `op1.opcode` to the empty string once `op1` + * is fully consumed. If `op1` is not fully consumed, `func` will be called again with the + * same `op1` value. If `op1` is fully consumed, the next call to `func` will be given the + * next operation from `in1`. If there are no more operations in `in1`, `op1.opcode` will be + * the empty string. + * - `op2` is the current operation from `in2`, to apply to `op1`. Has the same consumption + * and advancement semantics as `op1`. + * - `opOut` MUST be mutated to reflect the result of applying `op2` (before consumption) to + * `op1` (before consumption). If there is no result (perhaps `op1` and `op2` cancelled each + * other out), `opOut.opcode` MUST be set to the empty string. + * @returns {string} the integrated changeset */ exports.applyZip = (in1, idx1, in2, idx2, func) => { const iter1 = exports.opIterator(in1, idx1); @@ -867,9 +1004,10 @@ exports.applyZip = (in1, idx1, in2, idx2, func) => { }; /** - * Unpacks a string encoded Changeset into a proper Changeset object - * @params cs {string} String encoded Changeset - * @returns {Changeset} a Changeset class + * Parses an encoded changeset. + * + * @param {string} cs - The encoded changeset. + * @returns {Changeset} */ exports.unpack = (cs) => { const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; @@ -893,12 +1031,13 @@ exports.unpack = (cs) => { }; /** - * Packs Changeset object into a string - * @params oldLen {int} Old length of the Changeset - * @params newLen {int] New length of the Changeset - * @params opsStr {string} String encoding of the changes to be made - * @params bank {string} Charbank of the Changeset - * @returns {Changeset} a Changeset class + * Creates an encoded changeset. + * + * @param {number} oldLen - The length of the document before applying the changeset. + * @param {number} newLen - The length of the document after applying the changeset. + * @param {string} opsStr - Encoded operations to apply to the document. + * @param {string} bank - Characters for insert operations. + * @returns {string} The encoded changeset. */ exports.pack = (oldLen, newLen, opsStr, bank) => { const lenDiff = newLen - oldLen; @@ -910,9 +1049,11 @@ exports.pack = (oldLen, newLen, opsStr, bank) => { }; /** - * Applies a Changeset to a string - * @params cs {string} String encoded Changeset - * @params str {string} String to which a Changeset should be applied + * Applies a Changeset to a string. + * + * @param {string} cs - String encoded Changeset + * @param {string} str - String to which a Changeset should be applied + * @returns {string} */ exports.applyToText = (cs, str) => { const unpacked = exports.unpack(cs); @@ -956,9 +1097,10 @@ exports.applyToText = (cs, str) => { }; /** - * applies a changeset on an array of lines - * @param CS {Changeset} the changeset to be applied - * @param lines The lines to which the changeset needs to be applied + * Applies a changeset on an array of lines. + * + * @param {string} cs - the changeset to apply + * @param {string[]} lines - The lines to which the changeset needs to be applied */ exports.mutateTextLines = (cs, lines) => { const unpacked = exports.unpack(cs); @@ -984,10 +1126,12 @@ exports.mutateTextLines = (cs, lines) => { /** * Composes two attribute strings (see below) into one. - * @param att1 {string} first attribute string - * @param att2 {string} second attribue string - * @param resultIsMutaton {boolean} - * @param pool {AttribPool} attribute pool + * + * @param {string} att1 - first attribute string + * @param {string} att2 - second attribue string + * @param {boolean} resultIsMutation - + * @param {AttributePool} pool - attribute pool + * @returns {string} */ exports.composeAttributes = (att1, att2, resultIsMutation, pool) => { // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean. @@ -1046,13 +1190,15 @@ exports.composeAttributes = (att1, att2, resultIsMutation, pool) => { }; /** - * Function used as parameter for applyZip to apply a Changeset to an - * attribute + * Function used as parameter for applyZip to apply a Changeset to an attribute. + * + * @param {Op} attOp - The op from the sequence that is being operated on, either an attribution + * string or the earlier of two exportss being composed. + * @param {Op} csOp - + * @param {Op} opOut - Mutated to hold the result of applying `csOp` to `attOp`. + * @param {AttributePool} pool - Can be null if definitely not needed. */ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { - // attOp is the op from the sequence that is being operated on, either an - // attribution string or the earlier of two exportss being composed. - // pool can be null if definitely not needed. if (attOp.opcode === '-') { exports.copyOp(attOp, opOut); attOp.opcode = ''; @@ -1138,9 +1284,11 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { /** * Applies a Changeset to the attribs string of a AText. - * @param cs {string} Changeset - * @param astr {string} the attribs string of a AText - * @param pool {AttribsPool} the attibutes pool + * + * @param {string} cs - Changeset + * @param {string} astr - the attribs string of a AText + * @param {AttributePool} pool - the attibutes pool + * @returns {string} */ exports.applyToAttribution = (cs, astr, pool) => { const unpacked = exports.unpack(cs); @@ -1157,6 +1305,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => { // treat the attribution lines as text lines, mutating a line at a time const mut = exports.textLinesMutator(lines); + /** @type {?OpIter} */ let lineIter = null; const isNextMutOp = () => (lineIter && lineIter.hasNext()) || mut.hasMore(); @@ -1233,8 +1382,9 @@ exports.mutateAttributionLines = (cs, lines, pool) => { }; /** - * joins several Attribution lines - * @param theAlines collection of Attribution lines + * Joins several Attribution lines. + * + * @param {string[]} theAlines - collection of Attribution lines * @returns {string} joined Attribution lines */ exports.joinAttributionLines = (theAlines) => { @@ -1288,16 +1438,20 @@ exports.splitAttributionLines = (attrOps, text) => { }; /** - * splits text into lines - * @param {string} text to be splitted + * Splits text into lines. + * + * @param {string} text - text to split + * @returns {string[]} */ exports.splitTextLines = (text) => text.match(/[^\n]*(?:\n|[^\n]$)/g); /** - * compose two Changesets - * @param cs1 {Changeset} first Changeset - * @param cs2 {Changeset} second Changeset - * @param pool {AtribsPool} Attribs pool + * Compose two Changesets. + * + * @param {string} cs1 - first Changeset + * @param {string} cs2 - second Changeset + * @param {AttributePool} pool - Attribs pool + * @returns {string} */ exports.compose = (cs1, cs2, pool) => { const unpacked1 = exports.unpack(cs1); @@ -1330,11 +1484,12 @@ exports.compose = (cs1, cs2, pool) => { }; /** - * returns a function that tests if a string of attributes - * (e.g. *3*4) contains a given attribute key,value that - * is already present in the pool. - * @param attribPair array [key,value] of the attribute - * @param pool {AttribPool} Attribute pool + * Returns a function that tests if a string of attributes (e.g. '*3*4') contains a given attribute + * key,value that is already present in the pool. + * + * @param {[string, string]} attribPair - Array of attribute pairs. + * @param {AttributePool} pool - Attribute pool + * @returns {Function} */ exports.attributeTester = (attribPair, pool) => { const never = (attribs) => false; @@ -1351,23 +1506,25 @@ exports.attributeTester = (attribPair, pool) => { }; /** - * creates the identity Changeset of length N - * @param N {int} length of the identity changeset + * Creates the identity Changeset of length N. + * + * @param {number} N - length of the identity changeset + * @returns {string} */ exports.identity = (N) => exports.pack(N, N, '', ''); - /** - * creates a Changeset which works on oldFullText and removes text - * from spliceStart to spliceStart+numRemoved and inserts newText - * instead. Also gives possibility to add attributes optNewTextAPairs - * for the new text. - * @param oldFullText {string} old text - * @param spliecStart {int} where splicing starts - * @param numRemoved {int} number of characters to be removed - * @param newText {string} string to be inserted - * @param optNewTextAPairs {string} new pairs to be inserted - * @param pool {AttribPool} Attribution Pool + * Creates a Changeset which works on oldFullText and removes text from spliceStart to + * spliceStart+numRemoved and inserts newText instead. Also gives possibility to add attributes + * optNewTextAPairs for the new text. + * + * @param {string} oldFullText - old text + * @param {number} spliceStart - where splicing starts + * @param {number} numRemoved - number of characters to remove + * @param {string} newText - string to insert + * @param {string} optNewTextAPairs - new pairs to insert + * @param {AttributePool} pool - Attribute pool + * @returns {string} */ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) => { const oldLen = oldFullText.length; @@ -1390,13 +1547,15 @@ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextA }; /** - * Transforms a changeset into a list of splices in the form - * [startChar, endChar, newText] meaning replace text from - * startChar to endChar with newText - * @param cs Changeset + * Transforms a changeset into a list of splices in the form [startChar, endChar, newText] meaning + * replace text from startChar to endChar with newText. + * + * @param {string} cs - Changeset + * @returns {[number, number, string][]} */ exports.toSplices = (cs) => { const unpacked = exports.unpack(cs); + /** @type {[number, number, string][]} */ const splices = []; let oldPos = 0; @@ -1426,7 +1585,11 @@ exports.toSplices = (cs) => { }; /** - * + * @param {string} cs - + * @param {number} startChar - + * @param {number} endChar - + * @param {number} insertionsAfter - + * @returns {[number, number]} */ exports.characterRangeFollow = (cs, startChar, endChar, insertionsAfter) => { let newStartChar = startChar; @@ -1473,12 +1636,12 @@ exports.characterRangeFollow = (cs, startChar, endChar, insertionsAfter) => { }; /** - * Iterate over attributes in a changeset and move them from - * oldPool to newPool - * @param cs {Changeset} Chageset/attribution string to be iterated over - * @param oldPool {AttribPool} old attributes pool - * @param newPool {AttribPool} new attributes pool - * @return {string} the new Changeset + * Iterate over attributes in a changeset and move them from oldPool to newPool. + * + * @param {string} cs - Chageset/attribution string to iterate over + * @param {AttributePool} oldPool - old attributes pool + * @param {AttributePool} newPool - new attributes pool + * @returns {string} the new Changeset */ exports.moveOpsToNewPool = (cs, oldPool, newPool) => { // works on exports or attribution string @@ -1509,8 +1672,10 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => { }; /** - * create an attribution inserting a text - * @param text {string} text to be inserted + * Create an attribution inserting a text. + * + * @param {string} text - text to insert + * @returns {string} */ exports.makeAttribution = (text) => { const assem = exports.smartOpAssembler(); @@ -1519,10 +1684,11 @@ exports.makeAttribution = (text) => { }; /** - * Iterates over attributes in exports, attribution string, or attribs property of an op - * and runs function func on them - * @param cs {Changeset} changeset - * @param func {function} function to be called + * Iterates over attributes in exports, attribution string, or attribs property of an op and runs + * function func on them. + * + * @param {string} cs - changeset + * @param {Function} func - function to call */ exports.eachAttribNumber = (cs, func) => { let dollarPos = cs.indexOf('$'); @@ -1538,17 +1704,22 @@ exports.eachAttribNumber = (cs, func) => { }; /** - * Filter attributes which should remain in a Changeset - * callable on a exports, attribution string, or attribs property of an op, - * though it may easily create adjacent ops that can be merged. - * @param cs {Changeset} changeset to be filtered - * @param filter {function} fnc which returns true if an - * attribute X (int) should be kept in the Changeset + * Filter attributes which should remain in a Changeset. Callable on a exports, attribution string, + * or attribs property of an op, though it may easily create adjacent ops that can be merged. + * + * @param {string} cs - changeset to filter + * @param {Function} filter - fnc which returns true if an attribute X (int) should be kept in the + * Changeset + * @returns {string} */ exports.filterAttribNumbers = (cs, filter) => exports.mapAttribNumbers(cs, filter); /** - * does exactly the same as exports.filterAttribNumbers + * Does exactly the same as exports.filterAttribNumbers. + * + * @param {string} cs - + * @param {Function} func - + * @returns {string} */ exports.mapAttribNumbers = (cs, func) => { let dollarPos = cs.indexOf('$'); @@ -1572,10 +1743,21 @@ exports.mapAttribNumbers = (cs, func) => { }; /** - * Create a Changeset going from Identity to a certain state - * @params text {string} text of the final change - * @attribs attribs {string} optional, operations which insert - * the text and also puts the right attributes + * Represents text with attributes. + * + * @typedef {object} AText + * @property {string} attribs - Serialized sequence of insert operations that cover the text in + * `text`. These operations describe which parts of the text have what attributes. + * @property {string} text - The text. + */ + +/** + * Create a Changeset going from Identity to a certain state. + * + * @param {string} text - text of the final change + * @param {string} attribs - optional, operations which insert the text and also puts the right + * attributes + * @returns {AText} */ exports.makeAText = (text, attribs) => ({ text, @@ -1583,10 +1765,12 @@ exports.makeAText = (text, attribs) => ({ }); /** - * Apply a Changeset to a AText - * @param cs {Changeset} Changeset to be applied - * @param atext {AText} - * @param pool {AttribPool} Attribute Pool to add to + * Apply a Changeset to a AText. + * + * @param {string} cs - Changeset to apply + * @param {AText} atext - + * @param {AttributePool} pool - Attribute Pool to add to + * @returns {AText} */ exports.applyToAText = (cs, atext, pool) => ({ text: exports.applyToText(cs, atext.text), @@ -1594,8 +1778,10 @@ exports.applyToAText = (cs, atext, pool) => ({ }); /** - * Clones a AText structure - * @param atext {AText} + * Clones a AText structure. + * + * @param {AText} atext - + * @returns {AText} */ exports.cloneAText = (atext) => { if (atext) { @@ -1607,8 +1793,10 @@ exports.cloneAText = (atext) => { }; /** - * Copies a AText structure from atext1 to atext2 - * @param atext {AText} + * Copies a AText structure from atext1 to atext2. + * + * @param {AText} atext1 - + * @param {AText} atext2 - */ exports.copyAText = (atext1, atext2) => { atext2.text = atext1.text; @@ -1616,9 +1804,10 @@ exports.copyAText = (atext1, atext2) => { }; /** - * Append the set of operations from atext to an assembler - * @param atext {AText} - * @param assem Assembler like smartOpAssembler + * Append the set of operations from atext to an assembler. + * + * @param {AText} atext - + * @param assem - Assembler like SmartOpAssembler TODO add desc */ exports.appendATextToAssembler = (atext, assem) => { // intentionally skips last newline char of atext @@ -1654,9 +1843,11 @@ exports.appendATextToAssembler = (atext, assem) => { }; /** - * Creates a clone of a Changeset and it's APool - * @param cs {Changeset} - * @param pool {AtributePool} + * Creates a clone of a Changeset and it's APool. + * + * @param {string} cs - + * @param {AttributePool} pool - + * @returns {{translated: string, pool: AttributePool}} */ exports.prepareForWire = (cs, pool) => { const newPool = new AttributePool(); @@ -1668,7 +1859,10 @@ exports.prepareForWire = (cs, pool) => { }; /** - * Checks if a changeset s the identity changeset + * Checks if a changeset s the identity changeset. + * + * @param {string} cs - + * @returns {boolean} */ exports.isIdentity = (cs) => { const unpacked = exports.unpack(cs); @@ -1676,20 +1870,22 @@ exports.isIdentity = (cs) => { }; /** - * returns all the values of attributes with a certain key - * in an Op attribs string - * @param attribs {string} Attribute string of a Op - * @param key {string} string to be seached for - * @param pool {AttribPool} attribute pool + * Returns all the values of attributes with a certain key in an Op attribs string. + * + * @param {Op} op - Op + * @param {string} key - string to search for + * @param {AttributePool} pool - attribute pool + * @returns {string} */ exports.opAttributeValue = (op, key, pool) => exports.attribsAttributeValue(op.attribs, key, pool); /** - * returns all the values of attributes with a certain key - * in an attribs string - * @param attribs {string} Attribute string - * @param key {string} string to be seached for - * @param pool {AttribPool} attribute pool + * Returns all the values of attributes with a certain key in an attribs string. + * + * @param {string} attribs - Attribute string + * @param {string} key - string to search for + * @param {AttributePool} pool - attribute pool + * @returns {string} */ exports.attribsAttributeValue = (attribs, key, pool) => { let value = ''; @@ -1704,9 +1900,19 @@ exports.attribsAttributeValue = (attribs, key, pool) => { }; /** - * Creates a Changeset builder for a string with initial - * length oldLen. Allows to add/remove parts of it - * @param oldLen {int} Old length + * Incrementally builds a Changeset. + * + * @typedef {object} Builder + * @property {Function} insert - + * @property {Function} keep - + * @property {Function} keepText - + * @property {Function} remove - + * @property {Function} toString - + */ + +/** + * @param {number} oldLen - Old length + * @returns {Builder} */ exports.builder = (oldLen) => { const assem = exports.smartOpAssembler(); @@ -1714,7 +1920,10 @@ exports.builder = (oldLen) => { const charBank = exports.stringAssembler(); const self = { - // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) + /** + * @param attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in + * latter case). + */ keep: (N, L, attribs, pool) => { o.opcode = '='; o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || ''; @@ -1772,7 +1981,9 @@ exports.makeAttribsString = (opcode, attribs, pool) => { } }; -// like "substring" but on a single-line attribution string +/** + * Like "substring" but on a single-line attribution string. + */ exports.subattribution = (astr, start, optEnd) => { const iter = exports.opIterator(astr, 0); const assem = exports.smartOpAssembler(); @@ -1834,6 +2045,10 @@ exports.inverse = (cs, lines, alines, pool) => { } }; + /** + * @param {number} idx - + * @returns {string} + */ const alinesGet = (idx) => { if (alines.get) { return alines.get(idx);