Changeset: Migrate from `OpIter` to `deserializeOps()`

This commit is contained in:
Richard Hansen 2021-10-25 05:48:58 -04:00
parent 0eca0251f2
commit 89fe40e080
16 changed files with 147 additions and 179 deletions

View File

@ -21,6 +21,8 @@
* `eachAttribNumber()`
* `makeAttribsString()`
* `opAttributeValue()`
* `opIterator()`: Deprecated in favor of the new `deserializeOps()` generator
function.
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
generator function.
* `newOp()`: Deprecated in favor of the new `Op` class.

View File

@ -670,9 +670,8 @@ const Changeset = require('ep_etherpad-lite/static/js/Changeset');
exports.getLineHTMLForExport = async (hookName, context) => {
if (!context.attribLine) return;
const opIter = Changeset.opIterator(context.attribLine);
if (!opIter.hasNext()) return;
const op = opIter.next();
const [op] = Changeset.deserializeOps(context.attribLine);
if (op == null) return;
const heading = AttributeMap.fromString(op.attribs, context.apool).get('heading');
if (!heading) return;
context.lineContent = `<${heading}>${context.lineContent}</${heading}>`;

View File

@ -527,12 +527,10 @@ exports.restoreRevision = async (padID, rev) => {
atext.text += '\n';
const eachAttribRun = (attribs, func) => {
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
while (attribsIter.hasNext()) {
const op = attribsIter.next();
for (const op of Changeset.deserializeOps(attribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);

View File

@ -586,12 +586,7 @@ const handleUserChanges = async (socket, message) => {
Changeset.checkRep(changeset);
// Validate all added 'author' attribs to be the same value as the current user
const iterator = Changeset.opIterator(Changeset.unpack(changeset).ops);
let op;
while (iterator.hasNext()) {
op = iterator.next();
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
// + can add text with attribs
// = can change or add attribs
// - can have attribs, but they are discarded and don't show up in the attribs -
@ -741,10 +736,8 @@ const _correctMarkersInPad = (atext, apool) => {
// collect char positions of line markers (e.g. bullets) in new atext
// that aren't at the start of a line
const badMarkers = [];
const iter = Changeset.opIterator(atext.attribs);
let offset = 0;
while (iter.hasNext()) {
const op = iter.next();
for (const op of Changeset.deserializeOps(atext.attribs)) {
const attribs = AttributeMap.fromString(op.attribs, apool);
const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
if (hasMarker) {

View File

@ -52,9 +52,8 @@ exports._analyzeLine = (text, aline, apool) => {
let lineMarker = 0;
line.listLevel = 0;
if (aline) {
const opIter = Changeset.opIterator(aline);
if (opIter.hasNext()) {
const op = opIter.next();
const [op] = Changeset.deserializeOps(aline);
if (op != null) {
const attribs = AttributeMap.fromString(op.attribs, apool);
let listType = attribs.get('list');
if (listType) {

View File

@ -197,13 +197,12 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
return;
}
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
// this iterates over every op string and decides which tags to open or to close
// based on the attribs used
while (iter.hasNext()) {
const o = iter.next();
for (const o of ops) {
const usedAttribs = [];
// mark all attribs as used

View File

@ -76,11 +76,10 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
return;
}
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
while (iter.hasNext()) {
const o = iter.next();
for (const o of ops) {
let propChanged = false;
for (const a of attributes.decodeAttribString(o.attribs)) {

View File

@ -67,12 +67,10 @@ exports.setPadHTML = async (pad, html) => {
const builder = Changeset.builder(1);
// assemble each line into the builder
const attribsIter = Changeset.opIterator(newAttribs);
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = newText.length;
while (attribsIter.hasNext()) {
const op = attribsIter.next();
for (const op of Changeset.deserializeOps(newAttribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
const start = Math.max(newTextStart, textIndex);

View File

@ -35,16 +35,10 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
return false;
}
// lets iterator over the operators
const iterator = Changeset.opIterator(unpacked.ops);
// get the first operator, this should be a clear operator
const clearOperator = iterator.next();
const [clearOperator, anotherOp] = Changeset.deserializeOps(unpacked.ops);
// check if there is only one operator
if (iterator.hasNext() === true) {
return false;
}
if (anotherOp != null) return false;
// check if this operator doesn't change text
if (clearOperator.opcode !== '=') {
@ -212,7 +206,6 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
// unpack
const unpacked = Changeset.unpack(changeset);
const iterator = Changeset.opIterator(unpacked.ops);
const assem = Changeset.opAssembler();
// create deleted attribs
@ -220,10 +213,7 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
const deletedAttrib = apool.putAttrib(['removed', true]);
const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
// iteratore over the operators of the changeset
while (iterator.hasNext()) {
const operator = iterator.next();
for (const operator of Changeset.deserializeOps(unpacked.ops)) {
if (operator.opcode === '-') {
// this is a delete operator, extend it with the author
operator.attribs = attribs;
@ -268,22 +258,23 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
let curLine = 0;
let curChar = 0;
let curLineOpIter = null;
let curLineOpIterLine;
let curLineOps = null;
let curLineOpsNext = null;
let curLineOpsLine;
let curLineNextOp = new Changeset.Op('+');
const unpacked = Changeset.unpack(cs);
const csIter = Changeset.opIterator(unpacked.ops);
const builder = Changeset.builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
// create curLineOpIter and advance it to curChar
curLineOpIter = Changeset.opIterator(aLinesGet(curLine));
curLineOpIterLine = curLine;
if (!curLineOps || curLineOpsLine !== curLine) {
curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine;
let indexIntoLine = 0;
while (curLineOpIter.hasNext()) {
curLineNextOp = curLineOpIter.next();
while (!curLineOpsNext.done) {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
if (indexIntoLine + curLineNextOp.chars >= curChar) {
curLineNextOp.chars -= (curChar - indexIntoLine);
break;
@ -293,16 +284,22 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
}
while (numChars > 0) {
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
curLineOpIterLine = curLine;
curLineOpsLine = curLine;
curLineNextOp.chars = 0;
curLineOpIter = Changeset.opIterator(aLinesGet(curLine));
curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
}
if (!curLineNextOp.chars) {
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Changeset.Op();
if (curLineOpsNext.done) {
curLineNextOp = new Changeset.Op();
} else {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
}
}
const charsToUse = Math.min(numChars, curLineNextOp.chars);
@ -314,7 +311,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curChar += charsToUse;
}
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
}
@ -324,7 +321,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
if (L) {
curLine += L;
curChar = 0;
} else if (curLineOpIter && curLineOpIterLine === curLine) {
} else if (curLineOps && curLineOpsLine === curLine) {
consumeAttribRuns(N, () => {});
} else {
curChar += N;
@ -361,10 +358,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
};
};
// iterate over all operators of this changeset
while (csIter.hasNext()) {
const csOp = csIter.next();
for (const csOp of Changeset.deserializeOps(unpacked.ops)) {
if (csOp.opcode === '=') {
const textBank = nextText(csOp.chars);

View File

@ -150,9 +150,9 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
// get `attributeName` attribute of first char of line
const aline = this.rep.alines[lineNum];
if (!aline) return '';
const opIter = Changeset.opIterator(aline);
if (!opIter.hasNext()) return '';
return AttributeMap.fromString(opIter.next().attribs, this.rep.apool).get(attributeName) || '';
const [op] = Changeset.deserializeOps(aline);
if (op == null) return '';
return AttributeMap.fromString(op.attribs, this.rep.apool).get(attributeName) || '';
},
/*
@ -163,9 +163,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
// get attributes of first char of line
const aline = this.rep.alines[lineNum];
if (!aline) return [];
const opIter = Changeset.opIterator(aline);
if (!opIter.hasNext()) return [];
const op = opIter.next();
const [op] = Changeset.deserializeOps(aline);
if (op == null) return [];
return [...attributes.attribsFromString(op.attribs, this.rep.apool)];
},
@ -221,13 +220,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
const end = selEnd[1];
let hasAttrib = true;
// Iterate over attribs on this line
const opIter = Changeset.opIterator(rep.alines[lineNum]);
let indexIntoLine = 0;
while (opIter.hasNext()) {
const op = opIter.next();
for (const op of Changeset.deserializeOps(rep.alines[lineNum])) {
const opStartInLine = indexIntoLine;
const opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs)) {
@ -260,15 +254,11 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
if (!aline) {
return [];
}
// iterate through all operations of a line
const opIter = Changeset.opIterator(aline);
// we need to sum up how much characters each operations take until the wanted position
let currentPointer = 0;
let currentOperation;
while (opIter.hasNext()) {
currentOperation = opIter.next();
for (const currentOperation of Changeset.deserializeOps(aline)) {
currentPointer += currentOperation.chars;
if (currentPointer <= column) continue;
return [...attributes.attribsFromString(currentOperation.attribs, this.rep.apool)];

View File

@ -187,7 +187,7 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
* @yields {Op}
* @returns {Generator<Op>}
*/
const deserializeOps = function* (ops) {
exports.deserializeOps = function* (ops) {
// TODO: Migrate to String.prototype.matchAll() once there is enough browser support.
const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
let match;
@ -206,13 +206,15 @@ const deserializeOps = function* (ops) {
* Iterator over a changeset's operations.
*
* Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
*
* @deprecated Use `deserializeOps` instead.
*/
class OpIter {
/**
* @param {string} ops - String encoding the change operations to iterate over.
*/
constructor(ops) {
this._gen = deserializeOps(ops);
this._gen = exports.deserializeOps(ops);
this._next = this._gen.next();
}
@ -246,10 +248,15 @@ class OpIter {
/**
* Creates an iterator which decodes string changeset operations.
*
* @deprecated Use `deserializeOps` instead.
* @param {string} opsStr - String encoding of the change operations to perform.
* @returns {OpIter} Operator iterator object.
*/
exports.opIterator = (opsStr) => new OpIter(opsStr);
exports.opIterator = (opsStr) => {
padutils.warnWithStack(
'Changeset.opIterator() is deprecated; use Changeset.deserializeOps() instead');
return new OpIter(opsStr);
};
/**
* Cleans an Op object.
@ -374,9 +381,7 @@ exports.checkRep = (cs) => {
let oldPos = 0;
let calcNewLen = 0;
let numInserted = 0;
const iter = new OpIter(ops);
while (iter.hasNext()) {
const o = iter.next();
for (const o of exports.deserializeOps(ops)) {
switch (o.opcode) {
case '=':
oldPos += o.chars;
@ -1027,15 +1032,18 @@ class TextLinesMutator {
* @returns {string} the integrated changeset
*/
const applyZip = (in1, in2, func) => {
const iter1 = new OpIter(in1);
const iter2 = new OpIter(in2);
const ops1 = exports.deserializeOps(in1);
const ops2 = exports.deserializeOps(in2);
let next1 = ops1.next();
let next2 = ops2.next();
const assem = exports.smartOpAssembler();
const op1 = new Op();
const op2 = new Op();
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
const opOut = func(op1, op2);
while (!next1.done || !next2.done) {
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
if (next1.value == null) next1.value = new Op();
if (next2.value == null) next2.value = new Op();
if (!next1.value.opcode && !next2.value.opcode) break;
const opOut = func(next1.value, next2.value);
if (opOut && opOut.opcode) assem.append(opOut);
}
assem.endDocument();
@ -1097,12 +1105,10 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
exports.applyToText = (cs, str) => {
const unpacked = exports.unpack(cs);
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
const csIter = new OpIter(unpacked.ops);
const bankIter = exports.stringIterator(unpacked.charBank);
const strIter = exports.stringIterator(str);
const assem = exports.stringAssembler();
while (csIter.hasNext()) {
const op = csIter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
switch (op.opcode) {
case '+':
// op is + and op.lines 0: no newlines must be in op.chars
@ -1142,11 +1148,9 @@ exports.applyToText = (cs, str) => {
*/
exports.mutateTextLines = (cs, lines) => {
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const bankIter = exports.stringIterator(unpacked.charBank);
const mut = new TextLinesMutator(lines);
while (csIter.hasNext()) {
const op = csIter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
switch (op.opcode) {
case '+':
mut.insert(bankIter.take(op.chars), op.lines);
@ -1273,24 +1277,30 @@ exports.applyToAttribution = (cs, astr, pool) => {
exports.mutateAttributionLines = (cs, lines, pool) => {
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const csOps = exports.deserializeOps(unpacked.ops);
let csOpsNext = csOps.next();
const csBank = unpacked.charBank;
let csBankIndex = 0;
// treat the attribution lines as text lines, mutating a line at a time
const mut = new TextLinesMutator(lines);
/** @type {?OpIter} */
let lineIter = null;
/** @type {?Generator<Op>} */
let lineOps = null;
let lineOpsNext = null;
const isNextMutOp = () => (lineIter && lineIter.hasNext()) || mut.hasMore();
const lineOpsHasNext = () => lineOpsNext && !lineOpsNext.done;
const isNextMutOp = () => lineOpsHasNext() || mut.hasMore();
const nextMutOp = () => {
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
if (!lineOpsHasNext() && mut.hasMore()) {
const line = mut.removeLines(1);
lineIter = new OpIter(line);
lineOps = exports.deserializeOps(line);
lineOpsNext = lineOps.next();
}
if (!lineIter || !lineIter.hasNext()) return new Op();
return lineIter.next();
if (!lineOpsHasNext()) return new Op();
const op = lineOpsNext.value;
lineOpsNext = lineOps.next();
return op;
};
let lineAssem = null;
@ -1308,12 +1318,15 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
let csOp = new Op();
let attOp = new Op();
while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
if (!csOp.opcode && csIter.hasNext()) csOp = csIter.next();
if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
while (csOp.opcode || !csOpsNext.done || attOp.opcode || isNextMutOp()) {
if (!csOp.opcode && !csOpsNext.done) {
csOp = csOpsNext.value;
csOpsNext = csOps.next();
}
if (!csOp.opcode && !attOp.opcode && !lineAssem && !lineOpsHasNext()) {
break; // done
} else if (csOp.opcode === '=' && csOp.lines > 0 && (!csOp.attribs) &&
(!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
} else if (csOp.opcode === '=' && csOp.lines > 0 && !csOp.attribs && !attOp.opcode &&
!lineAssem && !lineOpsHasNext()) {
// skip multiple lines; this is what makes small changes not order of the document size
mut.skipLines(csOp.lines);
csOp.opcode = '';
@ -1350,16 +1363,12 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
exports.joinAttributionLines = (theAlines) => {
const assem = exports.mergingOpAssembler();
for (const aline of theAlines) {
const iter = new OpIter(aline);
while (iter.hasNext()) {
assem.append(iter.next());
}
for (const op of exports.deserializeOps(aline)) assem.append(op);
}
return assem.toString();
};
exports.splitAttributionLines = (attrOps, text) => {
const iter = new OpIter(attrOps);
const assem = exports.mergingOpAssembler();
const lines = [];
let pos = 0;
@ -1373,8 +1382,7 @@ exports.splitAttributionLines = (attrOps, text) => {
pos += op.chars;
};
while (iter.hasNext()) {
const op = iter.next();
for (const op of exports.deserializeOps(attrOps)) {
let numChars = op.chars;
let numLines = op.lines;
while (numLines > 1) {
@ -1517,11 +1525,9 @@ const toSplices = (cs) => {
const splices = [];
let oldPos = 0;
const iter = new OpIter(unpacked.ops);
const charIter = exports.stringIterator(unpacked.charBank);
let inSplice = false;
while (iter.hasNext()) {
const op = iter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
if (op.opcode === '=') {
oldPos += op.chars;
inSplice = false;
@ -1764,11 +1770,10 @@ exports.copyAText = (atext1, atext2) => {
*/
exports.opsFromAText = function* (atext) {
// intentionally skips last newline char of atext
const iter = new OpIter(atext.attribs);
let lastOp = null;
while (iter.hasNext()) {
for (const op of exports.deserializeOps(atext.attribs)) {
if (lastOp != null) yield lastOp;
lastOp = iter.next();
lastOp = op;
}
if (lastOp == null) return;
// exclude final newline
@ -1986,15 +1991,19 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
* Like "substring" but on a single-line attribution string.
*/
exports.subattribution = (astr, start, optEnd) => {
const iter = new OpIter(astr);
const attOps = exports.deserializeOps(astr);
let attOpsNext = attOps.next();
const assem = exports.smartOpAssembler();
let attOp = new Op();
const csOp = new Op();
const doCsOp = () => {
if (!csOp.chars) return;
while (csOp.opcode && (attOp.opcode || iter.hasNext())) {
if (!attOp.opcode) attOp = iter.next();
while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
if (!attOp.opcode) {
attOp = attOpsNext.value;
attOpsNext = attOps.next();
}
if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars &&
attOp.lines > 0 && csOp.lines <= 0) {
csOp.lines++;
@ -2013,7 +2022,10 @@ exports.subattribution = (astr, start, optEnd) => {
if (attOp.opcode) {
assem.append(attOp);
}
while (iter.hasNext()) assem.append(iter.next());
while (!attOpsNext.done) {
assem.append(attOpsNext.value);
attOpsNext = attOps.next();
}
} else {
csOp.opcode = '=';
csOp.chars = optEnd - start;
@ -2050,22 +2062,23 @@ exports.inverse = (cs, lines, alines, pool) => {
let curLine = 0;
let curChar = 0;
let curLineOpIter = null;
let curLineOpIterLine;
let curLineOps = null;
let curLineOpsNext = null;
let curLineOpsLine;
let curLineNextOp = new Op('+');
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const builder = exports.builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
// create curLineOpIter and advance it to curChar
curLineOpIter = new OpIter(alinesGet(curLine));
curLineOpIterLine = curLine;
if (!curLineOps || curLineOpsLine !== curLine) {
curLineOps = exports.deserializeOps(alinesGet(curLine));
curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine;
let indexIntoLine = 0;
while (curLineOpIter.hasNext()) {
curLineNextOp = curLineOpIter.next();
while (!curLineOpsNext.done) {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
if (indexIntoLine + curLineNextOp.chars >= curChar) {
curLineNextOp.chars -= (curChar - indexIntoLine);
break;
@ -2075,15 +2088,21 @@ exports.inverse = (cs, lines, alines, pool) => {
}
while (numChars > 0) {
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
curLineOpIterLine = curLine;
curLineOpsLine = curLine;
curLineNextOp.chars = 0;
curLineOpIter = new OpIter(alinesGet(curLine));
curLineOps = exports.deserializeOps(alinesGet(curLine));
curLineOpsNext = curLineOps.next();
}
if (!curLineNextOp.chars) {
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
if (curLineOpsNext.done) {
curLineNextOp = new Op();
} else {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
}
}
const charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
@ -2093,7 +2112,7 @@ exports.inverse = (cs, lines, alines, pool) => {
curChar += charsToUse;
}
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
}
@ -2103,7 +2122,7 @@ exports.inverse = (cs, lines, alines, pool) => {
if (L) {
curLine += L;
curChar = 0;
} else if (curLineOpIter && curLineOpIterLine === curLine) {
} else if (curLineOps && curLineOpsLine === curLine) {
consumeAttribRuns(N, () => {});
} else {
curChar += N;
@ -2138,8 +2157,7 @@ exports.inverse = (cs, lines, alines, pool) => {
};
};
while (csIter.hasNext()) {
const csOp = csIter.next();
for (const csOp of exports.deserializeOps(unpacked.ops)) {
if (csOp.opcode === '=') {
if (csOp.attribs) {
const attribs = AttributeMap.fromString(csOp.attribs, pool);

View File

@ -1576,13 +1576,8 @@ function Ace2Inner(editorInfo, cssManagers) {
const end = selEnd[1];
let hasAttrib = true;
// Iterate over attribs on this line
const opIter = Changeset.opIterator(rep.alines[lineNum]);
let indexIntoLine = 0;
while (opIter.hasNext()) {
const op = opIter.next();
for (const op of Changeset.deserializeOps(rep.alines[lineNum])) {
const opStartInLine = indexIntoLine;
const opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs)) {
@ -1615,7 +1610,6 @@ function Ace2Inner(editorInfo, cssManagers) {
const selStartLine = rep.selStart[0];
const selEndLine = rep.selEnd[0];
for (let n = selStartLine; n <= selEndLine; n++) {
const opIter = Changeset.opIterator(rep.alines[n]);
let indexIntoLine = 0;
let selectionStartInLine = 0;
if (documentAttributeManager.lineHasMarker(n)) {
@ -1628,8 +1622,7 @@ function Ace2Inner(editorInfo, cssManagers) {
if (n === selEndLine) {
selectionEndInLine = rep.selEnd[1];
}
while (opIter.hasNext()) {
const op = opIter.next();
for (const op of Changeset.deserializeOps(rep.alines[n])) {
const opStartInLine = indexIntoLine;
const opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs)) {
@ -1754,12 +1747,10 @@ function Ace2Inner(editorInfo, cssManagers) {
};
const eachAttribRun = (attribs, func /* (startInNewText, endInNewText, attribs)*/) => {
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = commonStart;
const newTextEnd = newText.length - commonEnd - (shiftFinalNewlineToBeforeNewText ? 1 : 0);
while (attribsIter.hasNext()) {
const op = attribsIter.next();
for (const op of Changeset.deserializeOps(attribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
@ -1873,9 +1864,7 @@ function Ace2Inner(editorInfo, cssManagers) {
const attribRuns = (attribs) => {
const lengs = [];
const atts = [];
const iter = Changeset.opIterator(attribs);
while (iter.hasNext()) {
const op = iter.next();
for (const op of Changeset.deserializeOps(attribs)) {
lengs.push(op.chars);
atts.push(op.attribs);
}
@ -2619,9 +2608,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// TODO: There appears to be a race condition or so.
const authorIds = new Set();
if (alineAttrs) {
const opIter = Changeset.opIterator(alineAttrs);
while (opIter.hasNext()) {
const op = opIter.next();
for (const op of Changeset.deserializeOps(alineAttrs)) {
const authorId = AttributeMap.fromString(op.attribs, apool).get('author');
if (authorId) authorIds.add(authorId);
}

View File

@ -162,13 +162,8 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// some chars are replaced (no attributes change and no length change)
// test if there are keep ops at the start of the cs
if (lineChanged === undefined) {
lineChanged = 0;
const opIter = Changeset.opIterator(Changeset.unpack(changeset).ops);
if (opIter.hasNext()) {
const op = opIter.next();
if (op.opcode === '=') lineChanged += op.lines;
}
const [op] = Changeset.deserializeOps(Changeset.unpack(changeset).ops);
lineChanged = op != null && op.opcode === '=' ? op.lines : 0;
}
const goToLineNumber = (lineNumber) => {

View File

@ -143,12 +143,9 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
// Sanitize authorship: Replace all author attributes with this user's author ID in case the
// text was copied from another author.
const cs = Changeset.unpack(userChangeset);
const iterator = Changeset.opIterator(cs.ops);
let op;
const assem = Changeset.mergingOpAssembler();
while (iterator.hasNext()) {
op = iterator.next();
for (const op of Changeset.deserializeOps(cs.ops)) {
if (op.opcode === '+') {
const attribs = AttributeMap.fromString(op.attribs, apool);
const oldAuthorId = attribs.get('author');

View File

@ -98,11 +98,13 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
return classes.substring(1);
};
const attributionIter = Changeset.opIterator(aline);
const attrOps = Changeset.deserializeOps(aline);
let attrOpsNext = attrOps.next();
let nextOp, nextOpClasses;
const goNextOp = () => {
nextOp = attributionIter.hasNext() ? attributionIter.next() : new Changeset.Op();
nextOp = attrOpsNext.done ? new Changeset.Op() : attrOpsNext.value;
if (!attrOpsNext.done) attrOpsNext = attrOps.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
};
goNextOp();

View File

@ -31,17 +31,15 @@ const randInt = (maxValue) => Math.floor(Math.random() * maxValue);
describe('easysync', function () {
it('throughIterator', async function () {
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
const iter = Changeset.opIterator(x);
const assem = Changeset.opAssembler();
while (iter.hasNext()) assem.append(iter.next());
for (const op of Changeset.deserializeOps(x)) assem.append(op);
expect(assem.toString()).to.equal(x);
});
it('throughSmartAssembler', async function () {
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
const iter = Changeset.opIterator(x);
const assem = Changeset.smartOpAssembler();
while (iter.hasNext()) assem.append(iter.next());
for (const op of Changeset.deserializeOps(x)) assem.append(op);
assem.endDocument();
expect(assem.toString()).to.equal(x);
});
@ -730,7 +728,7 @@ describe('easysync', function () {
p.putAttrib(['name', 'david']);
p.putAttrib(['color', 'green']);
const stringOp = (str) => Changeset.opIterator(str).next();
const stringOp = (str) => Changeset.deserializeOps(str).next().value;
expect(Changeset.opAttributeValue(stringOp('*0*1+1'), 'name', p)).to.equal('david');
expect(Changeset.opAttributeValue(stringOp('*0+1'), 'name', p)).to.equal('david');