mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
Changeset: Turn newOp()
into a real class
This commit is contained in:
parent
fba0bb6dff
commit
657492e191
7 changed files with 99 additions and 61 deletions
|
@ -23,6 +23,7 @@
|
||||||
* `opAttributeValue()`
|
* `opAttributeValue()`
|
||||||
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
|
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
|
||||||
generator function.
|
generator function.
|
||||||
|
* `newOp()`: Deprecated in favor of the new `Op` class.
|
||||||
|
|
||||||
# 1.8.15
|
# 1.8.15
|
||||||
|
|
||||||
|
|
|
@ -270,7 +270,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
||||||
let curChar = 0;
|
let curChar = 0;
|
||||||
let curLineOpIter = null;
|
let curLineOpIter = null;
|
||||||
let curLineOpIterLine;
|
let curLineOpIterLine;
|
||||||
let curLineNextOp = Changeset.newOp('+');
|
let curLineNextOp = new Changeset.Op('+');
|
||||||
|
|
||||||
const unpacked = Changeset.unpack(cs);
|
const unpacked = Changeset.unpack(cs);
|
||||||
const csIter = Changeset.opIterator(unpacked.ops);
|
const csIter = Changeset.opIterator(unpacked.ops);
|
||||||
|
@ -302,7 +302,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!curLineNextOp.chars) {
|
if (!curLineNextOp.chars) {
|
||||||
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : Changeset.newOp();
|
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Changeset.Op();
|
||||||
}
|
}
|
||||||
|
|
||||||
const charsToUse = Math.min(numChars, curLineNextOp.chars);
|
const charsToUse = Math.min(numChars, curLineNextOp.chars);
|
||||||
|
|
|
@ -83,9 +83,14 @@ exports.numToString = (num) => num.toString(36).toLowerCase();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An operation to apply to a shared document.
|
* An operation to apply to a shared document.
|
||||||
*
|
*/
|
||||||
* @typedef {object} Op
|
class Op {
|
||||||
* @property {('+'|'-'|'='|'')} opcode - The operation's operator:
|
/**
|
||||||
|
* @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
|
||||||
|
*/
|
||||||
|
constructor(opcode = '') {
|
||||||
|
/**
|
||||||
|
* The operation's operator:
|
||||||
* - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
|
* - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
|
||||||
* document.
|
* document.
|
||||||
* - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
|
* - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
|
||||||
|
@ -94,20 +99,58 @@ exports.numToString = (num) => num.toString(36).toLowerCase();
|
||||||
* the document. The inserted characters come from the changeset's character bank.
|
* 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
|
* - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an
|
||||||
* operation.
|
* 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
|
* @type {(''|'='|'+'|'-')}
|
||||||
* newlines. If non-zero, the last character must be a newline.
|
* @public
|
||||||
* @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)
|
this.opcode = opcode;
|
||||||
* 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 attribute pool. This is the empty
|
/**
|
||||||
* string for remove ('-') operations. For keep ('=') operations, the attributes are merged with
|
* The number of characters to keep, insert, or delete.
|
||||||
* the base text's existing attributes:
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.chars = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of characters among the `chars` characters that are newlines. If non-zero, the
|
||||||
|
* last character must be a newline.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.lines = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 attribute pool.
|
||||||
|
*
|
||||||
|
* 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
|
* - A keep op attribute with a non-empty value replaces an existing base text attribute that
|
||||||
* has the same key.
|
* has the same key.
|
||||||
* - A keep op attribute with an empty value is interpreted as an instruction to remove an
|
* - 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.
|
* existing base text attribute that has the same key, if one exists.
|
||||||
|
*
|
||||||
|
* This is the empty string for remove ('-') operations.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
|
this.attribs = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
if (!this.opcode) throw new TypeError('null op');
|
||||||
|
if (typeof this.attribs !== 'string') throw new TypeError('attribs must be a string');
|
||||||
|
const l = this.lines ? `|${exports.numToString(this.lines)}` : '';
|
||||||
|
return this.attribs + l + this.opcode + exports.numToString(this.chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Op = Op;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes changes to apply to a document. Does not include the attribute pool or the original
|
* Describes changes to apply to a document. Does not include the attribute pool or the original
|
||||||
|
@ -166,8 +209,7 @@ exports.opIterator = (opsStr) => {
|
||||||
};
|
};
|
||||||
let regexResult = nextRegexMatch();
|
let regexResult = nextRegexMatch();
|
||||||
|
|
||||||
const next = (optOp) => {
|
const next = (op = new Op()) => {
|
||||||
const op = optOp || exports.newOp();
|
|
||||||
if (regexResult[0]) {
|
if (regexResult[0]) {
|
||||||
op.attribs = regexResult[1];
|
op.attribs = regexResult[1];
|
||||||
op.lines = exports.parseNum(regexResult[2] || '0');
|
op.lines = exports.parseNum(regexResult[2] || '0');
|
||||||
|
@ -203,15 +245,14 @@ const clearOp = (op) => {
|
||||||
/**
|
/**
|
||||||
* Creates a new Op object
|
* Creates a new Op object
|
||||||
*
|
*
|
||||||
|
* @deprecated Use the `Op` class instead.
|
||||||
* @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator.
|
* @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator.
|
||||||
* @returns {Op}
|
* @returns {Op}
|
||||||
*/
|
*/
|
||||||
exports.newOp = (optOpcode) => ({
|
exports.newOp = (optOpcode) => {
|
||||||
opcode: (optOpcode || ''),
|
padutils.warnWithStack('Changeset.newOp() is deprecated; use the Changeset.Op class instead');
|
||||||
chars: 0,
|
return new Op(optOpcode);
|
||||||
lines: 0,
|
};
|
||||||
attribs: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies op1 to op2
|
* Copies op1 to op2
|
||||||
|
@ -220,7 +261,7 @@ exports.newOp = (optOpcode) => ({
|
||||||
* @param {Op} [op2] - dest Op. If not given, a new Op is used.
|
* @param {Op} [op2] - dest Op. If not given, a new Op is used.
|
||||||
* @returns {Op} `op2`
|
* @returns {Op} `op2`
|
||||||
*/
|
*/
|
||||||
const copyOp = (op1, op2 = exports.newOp()) => Object.assign(op2, op1);
|
const copyOp = (op1, op2 = new Op()) => Object.assign(op2, op1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes a sequence of Ops.
|
* Serializes a sequence of Ops.
|
||||||
|
@ -257,7 +298,7 @@ const copyOp = (op1, op2 = exports.newOp()) => Object.assign(op2, op1);
|
||||||
* @returns {Generator<Op>}
|
* @returns {Generator<Op>}
|
||||||
*/
|
*/
|
||||||
const opsFromText = function* (opcode, text, attribs = '', pool = null) {
|
const opsFromText = function* (opcode, text, attribs = '', pool = null) {
|
||||||
const op = exports.newOp(opcode);
|
const op = new Op(opcode);
|
||||||
op.attribs = typeof attribs === 'string'
|
op.attribs = typeof attribs === 'string'
|
||||||
? attribs : new AttributeMap(pool).update(attribs || [], opcode === '+').toString();
|
? attribs : new AttributeMap(pool).update(attribs || [], opcode === '+').toString();
|
||||||
const lastNewlinePos = text.lastIndexOf('\n');
|
const lastNewlinePos = text.lastIndexOf('\n');
|
||||||
|
@ -447,7 +488,7 @@ exports.smartOpAssembler = () => {
|
||||||
*/
|
*/
|
||||||
exports.mergingOpAssembler = () => {
|
exports.mergingOpAssembler = () => {
|
||||||
const assem = exports.opAssembler();
|
const assem = exports.opAssembler();
|
||||||
const bufOp = exports.newOp();
|
const bufOp = new Op();
|
||||||
|
|
||||||
// If we get, for example, insertions [xxx\n,yyy], those don't merge,
|
// If we get, for example, insertions [xxx\n,yyy], those don't merge,
|
||||||
// but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
|
// but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
|
||||||
|
@ -523,12 +564,8 @@ exports.opAssembler = () => {
|
||||||
* @param {Op} op - Operation to add. Ownership remains with the caller.
|
* @param {Op} op - Operation to add. Ownership remains with the caller.
|
||||||
*/
|
*/
|
||||||
const append = (op) => {
|
const append = (op) => {
|
||||||
if (!op.opcode) throw new TypeError('null op');
|
assert(op instanceof Op, 'argument must be an instance of Op');
|
||||||
if (typeof op.attribs !== 'string') throw new TypeError('attribs must be a string');
|
serialized += op.toString();
|
||||||
serialized += op.attribs;
|
|
||||||
if (op.lines) serialized += `|${exports.numToString(op.lines)}`;
|
|
||||||
serialized += op.opcode;
|
|
||||||
serialized += exports.numToString(op.chars);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toString = () => serialized;
|
const toString = () => serialized;
|
||||||
|
@ -972,8 +1009,8 @@ const applyZip = (in1, in2, func) => {
|
||||||
const iter1 = exports.opIterator(in1);
|
const iter1 = exports.opIterator(in1);
|
||||||
const iter2 = exports.opIterator(in2);
|
const iter2 = exports.opIterator(in2);
|
||||||
const assem = exports.smartOpAssembler();
|
const assem = exports.smartOpAssembler();
|
||||||
const op1 = exports.newOp();
|
const op1 = new Op();
|
||||||
const op2 = exports.newOp();
|
const op2 = new Op();
|
||||||
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
|
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
|
||||||
if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
|
if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
|
||||||
if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
|
if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
|
||||||
|
@ -1148,7 +1185,7 @@ exports.composeAttributes = (att1, att2, resultIsMutation, pool) => {
|
||||||
* @returns {Op} The result of applying `csOp` to `attOp`.
|
* @returns {Op} The result of applying `csOp` to `attOp`.
|
||||||
*/
|
*/
|
||||||
const slicerZipperFunc = (attOp, csOp, pool) => {
|
const slicerZipperFunc = (attOp, csOp, pool) => {
|
||||||
const opOut = exports.newOp();
|
const opOut = new Op();
|
||||||
if (!attOp.opcode) {
|
if (!attOp.opcode) {
|
||||||
copyOp(csOp, opOut);
|
copyOp(csOp, opOut);
|
||||||
csOp.opcode = '';
|
csOp.opcode = '';
|
||||||
|
@ -1231,7 +1268,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||||
const line = mut.removeLines(1);
|
const line = mut.removeLines(1);
|
||||||
lineIter = exports.opIterator(line);
|
lineIter = exports.opIterator(line);
|
||||||
}
|
}
|
||||||
if (!lineIter || !lineIter.hasNext()) return exports.newOp();
|
if (!lineIter || !lineIter.hasNext()) return new Op();
|
||||||
return lineIter.next();
|
return lineIter.next();
|
||||||
};
|
};
|
||||||
let lineAssem = null;
|
let lineAssem = null;
|
||||||
|
@ -1248,8 +1285,8 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||||
lineAssem = null;
|
lineAssem = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
let csOp = exports.newOp();
|
let csOp = new Op();
|
||||||
let attOp = exports.newOp();
|
let attOp = new Op();
|
||||||
while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
|
while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
|
||||||
if (!csOp.opcode && csIter.hasNext()) csOp = csIter.next();
|
if (!csOp.opcode && csIter.hasNext()) csOp = csIter.next();
|
||||||
if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
|
if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
|
||||||
|
@ -1826,7 +1863,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
|
||||||
*/
|
*/
|
||||||
exports.builder = (oldLen) => {
|
exports.builder = (oldLen) => {
|
||||||
const assem = exports.smartOpAssembler();
|
const assem = exports.smartOpAssembler();
|
||||||
const o = exports.newOp();
|
const o = new Op();
|
||||||
const charBank = exports.stringAssembler();
|
const charBank = exports.stringAssembler();
|
||||||
|
|
||||||
const self = {
|
const self = {
|
||||||
|
@ -1930,8 +1967,8 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
|
||||||
exports.subattribution = (astr, start, optEnd) => {
|
exports.subattribution = (astr, start, optEnd) => {
|
||||||
const iter = exports.opIterator(astr);
|
const iter = exports.opIterator(astr);
|
||||||
const assem = exports.smartOpAssembler();
|
const assem = exports.smartOpAssembler();
|
||||||
let attOp = exports.newOp();
|
let attOp = new Op();
|
||||||
const csOp = exports.newOp();
|
const csOp = new Op();
|
||||||
|
|
||||||
const doCsOp = () => {
|
const doCsOp = () => {
|
||||||
if (!csOp.chars) return;
|
if (!csOp.chars) return;
|
||||||
|
@ -1994,7 +2031,7 @@ exports.inverse = (cs, lines, alines, pool) => {
|
||||||
let curChar = 0;
|
let curChar = 0;
|
||||||
let curLineOpIter = null;
|
let curLineOpIter = null;
|
||||||
let curLineOpIterLine;
|
let curLineOpIterLine;
|
||||||
let curLineNextOp = exports.newOp('+');
|
let curLineNextOp = new Op('+');
|
||||||
|
|
||||||
const unpacked = exports.unpack(cs);
|
const unpacked = exports.unpack(cs);
|
||||||
const csIter = exports.opIterator(unpacked.ops);
|
const csIter = exports.opIterator(unpacked.ops);
|
||||||
|
@ -2025,7 +2062,7 @@ exports.inverse = (cs, lines, alines, pool) => {
|
||||||
curLineOpIter = exports.opIterator(alinesGet(curLine));
|
curLineOpIter = exports.opIterator(alinesGet(curLine));
|
||||||
}
|
}
|
||||||
if (!curLineNextOp.chars) {
|
if (!curLineNextOp.chars) {
|
||||||
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : exports.newOp();
|
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
|
||||||
}
|
}
|
||||||
const charsToUse = Math.min(numChars, curLineNextOp.chars);
|
const charsToUse = Math.min(numChars, curLineNextOp.chars);
|
||||||
func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
|
func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
|
||||||
|
@ -2135,7 +2172,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => {
|
||||||
const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
|
const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
|
||||||
|
|
||||||
const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
|
const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
|
||||||
const opOut = exports.newOp();
|
const opOut = new Op();
|
||||||
if (op1.opcode === '+' || op2.opcode === '+') {
|
if (op1.opcode === '+' || op2.opcode === '+') {
|
||||||
let whichToDo;
|
let whichToDo;
|
||||||
if (op2.opcode !== '+') {
|
if (op2.opcode !== '+') {
|
||||||
|
|
|
@ -523,7 +523,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
||||||
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
|
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
|
||||||
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
|
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
|
||||||
const assem = Changeset.smartOpAssembler();
|
const assem = Changeset.smartOpAssembler();
|
||||||
const o = Changeset.newOp('-');
|
const o = new Changeset.Op('-');
|
||||||
o.chars = upToLastLine;
|
o.chars = upToLastLine;
|
||||||
o.lines = numLines - 1;
|
o.lines = numLines - 1;
|
||||||
assem.append(o);
|
assem.append(o);
|
||||||
|
|
|
@ -83,7 +83,7 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
|
||||||
const textArray = [];
|
const textArray = [];
|
||||||
const attribsArray = [];
|
const attribsArray = [];
|
||||||
let attribsBuilder = null;
|
let attribsBuilder = null;
|
||||||
const op = Changeset.newOp('+');
|
const op = new Changeset.Op('+');
|
||||||
const self = {
|
const self = {
|
||||||
length: () => textArray.length,
|
length: () => textArray.length,
|
||||||
atColumnZero: () => textArray[textArray.length - 1] === '',
|
atColumnZero: () => textArray[textArray.length - 1] === '',
|
||||||
|
|
|
@ -102,7 +102,7 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
|
||||||
let nextOp, nextOpClasses;
|
let nextOp, nextOpClasses;
|
||||||
|
|
||||||
const goNextOp = () => {
|
const goNextOp = () => {
|
||||||
nextOp = attributionIter.hasNext() ? attributionIter.next() : Changeset.newOp();
|
nextOp = attributionIter.hasNext() ? attributionIter.next() : new Changeset.Op();
|
||||||
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
|
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
|
||||||
};
|
};
|
||||||
goNextOp();
|
goNextOp();
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe('easysync', function () {
|
||||||
|
|
||||||
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
|
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
|
||||||
const assem = Changeset.smartOpAssembler();
|
const assem = Changeset.smartOpAssembler();
|
||||||
const op = Changeset.newOp();
|
const op = new Changeset.Op();
|
||||||
const bank = Changeset.stringAssembler();
|
const bank = Changeset.stringAssembler();
|
||||||
let oldPos = 0;
|
let oldPos = 0;
|
||||||
let newLen = 0;
|
let newLen = 0;
|
||||||
|
@ -507,7 +507,7 @@ describe('easysync', function () {
|
||||||
const opAssem = Changeset.smartOpAssembler();
|
const opAssem = Changeset.smartOpAssembler();
|
||||||
const oldLen = origText.length;
|
const oldLen = origText.length;
|
||||||
|
|
||||||
const nextOp = Changeset.newOp();
|
const nextOp = new Changeset.Op();
|
||||||
|
|
||||||
const appendMultilineOp = (opcode, txt) => {
|
const appendMultilineOp = (opcode, txt) => {
|
||||||
nextOp.opcode = opcode;
|
nextOp.opcode = opcode;
|
||||||
|
@ -651,7 +651,7 @@ describe('easysync', function () {
|
||||||
const testSplitJoinAttributionLines = (randomSeed) => {
|
const testSplitJoinAttributionLines = (randomSeed) => {
|
||||||
const stringToOps = (str) => {
|
const stringToOps = (str) => {
|
||||||
const assem = Changeset.mergingOpAssembler();
|
const assem = Changeset.mergingOpAssembler();
|
||||||
const o = Changeset.newOp('+');
|
const o = new Changeset.Op('+');
|
||||||
o.chars = 1;
|
o.chars = 1;
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
const c = str.charAt(i);
|
const c = str.charAt(i);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue