mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 16:36:15 -04:00
Changeset: Use a generator to implement MergingOpAssembler
Eventually all uses of the class will be switched to the generator.
This commit is contained in:
parent
b5486b6753
commit
daa6b9074a
1 changed files with 66 additions and 41 deletions
|
@ -330,6 +330,63 @@ class OpAssembler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines consecutive operations when possible. Also skips no-op changes.
|
||||||
|
*
|
||||||
|
* @param {Iterable<Op>} ops - Iterable of operations to combine.
|
||||||
|
* @param {boolean} finalize - If truthy, omits the final op if it is an attributeless keep op.
|
||||||
|
* @yields {Op} The squashed operations.
|
||||||
|
* @returns {Generator<Op>}
|
||||||
|
*/
|
||||||
|
const squashOps = function* (ops, finalize) {
|
||||||
|
let prevOp = new Op();
|
||||||
|
// 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]. This variable stores the length of yyy and
|
||||||
|
// any other newline-less ops immediately after it.
|
||||||
|
let prevOpAdditionalCharsAfterNewline = 0;
|
||||||
|
|
||||||
|
const flush = function* (finalize) {
|
||||||
|
if (!prevOp.opcode) return;
|
||||||
|
if (finalize && prevOp.opcode === '=' && !prevOp.attribs) {
|
||||||
|
// final merged keep, leave it implicit
|
||||||
|
} else {
|
||||||
|
yield prevOp;
|
||||||
|
if (prevOpAdditionalCharsAfterNewline) {
|
||||||
|
const op = new Op(prevOp.opcode);
|
||||||
|
op.chars = prevOpAdditionalCharsAfterNewline;
|
||||||
|
op.lines = 0;
|
||||||
|
op.attribs = prevOp.attribs;
|
||||||
|
yield op;
|
||||||
|
prevOpAdditionalCharsAfterNewline = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevOp = new Op();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const op of ops) {
|
||||||
|
if (!op.opcode || op.chars <= 0) continue;
|
||||||
|
if (prevOp.opcode === op.opcode && prevOp.attribs === op.attribs) {
|
||||||
|
if (op.lines > 0) {
|
||||||
|
// bufOp and additional chars are all mergeable into a multi-line op
|
||||||
|
prevOp.chars += prevOpAdditionalCharsAfterNewline + op.chars;
|
||||||
|
prevOp.lines += op.lines;
|
||||||
|
prevOpAdditionalCharsAfterNewline = 0;
|
||||||
|
} else if (prevOp.lines === 0) {
|
||||||
|
// both prevOp and op are in-line
|
||||||
|
prevOp.chars += op.chars;
|
||||||
|
} else {
|
||||||
|
// append in-line text to multi-line prevOp
|
||||||
|
prevOpAdditionalCharsAfterNewline += op.chars;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield* flush(false);
|
||||||
|
prevOp = copyOp(op); // prevOp is mutated, so make a copy to protect op.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield* flush(finalize);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
|
* Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
|
||||||
* pure "keeps". It does not re-order operations.
|
* pure "keeps". It does not re-order operations.
|
||||||
|
@ -340,58 +397,26 @@ class MergingOpAssembler {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._assem = [];
|
this._ops = [];
|
||||||
this._bufOp = new Op();
|
this._serialized = null;
|
||||||
// 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]. This variable stores the length of yyy and
|
|
||||||
// any other newline-less ops immediately after it.
|
|
||||||
this._bufOpAdditionalCharsAfterNewline = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_flush(isEndDocument) {
|
_serialize(finalize) {
|
||||||
if (!this._bufOp.opcode) return;
|
this._serialized = exports.serializeOps(squashOps(this._ops, finalize));
|
||||||
if (isEndDocument && this._bufOp.opcode === '=' && !this._bufOp.attribs) {
|
|
||||||
// final merged keep, leave it implicit
|
|
||||||
} else {
|
|
||||||
this._assem.push(copyOp(this._bufOp));
|
|
||||||
if (this._bufOpAdditionalCharsAfterNewline) {
|
|
||||||
this._bufOp.chars = this._bufOpAdditionalCharsAfterNewline;
|
|
||||||
this._bufOp.lines = 0;
|
|
||||||
this._assem.push(copyOp(this._bufOp));
|
|
||||||
this._bufOpAdditionalCharsAfterNewline = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._bufOp.opcode = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
append(op) {
|
append(op) {
|
||||||
if (op.chars <= 0) return;
|
this._serialized = null;
|
||||||
if (this._bufOp.opcode === op.opcode && this._bufOp.attribs === op.attribs) {
|
this._ops.push(copyOp(op));
|
||||||
if (op.lines > 0) {
|
|
||||||
// this._bufOp and additional chars are all mergeable into a multi-line op
|
|
||||||
this._bufOp.chars += this._bufOpAdditionalCharsAfterNewline + op.chars;
|
|
||||||
this._bufOp.lines += op.lines;
|
|
||||||
this._bufOpAdditionalCharsAfterNewline = 0;
|
|
||||||
} else if (this._bufOp.lines === 0) {
|
|
||||||
// both this._bufOp and op are in-line
|
|
||||||
this._bufOp.chars += op.chars;
|
|
||||||
} else {
|
|
||||||
// append in-line text to multi-line this._bufOp
|
|
||||||
this._bufOpAdditionalCharsAfterNewline += op.chars;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._flush();
|
|
||||||
copyOp(op, this._bufOp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endDocument() {
|
endDocument() {
|
||||||
this._flush(true);
|
this._serialize(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
this._flush();
|
if (this._serialized == null) this._serialize(false);
|
||||||
return exports.serializeOps(this._assem);
|
return this._serialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue