Changeset: Migrate from OpIter
to deserializeOps()
This commit is contained in:
parent
0eca0251f2
commit
89fe40e080
16 changed files with 147 additions and 179 deletions
|
@ -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.
|
||||
|
|
|
@ -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}>`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in a new issue