Skip to content

Commit 542cabc

Browse files
committed
Changeset: Use a generator to implement SmartOpAssembler
Eventually all uses of the class will be switched to the generator.
1 parent 06df190 commit 542cabc

File tree

1 file changed

+72
-47
lines changed

1 file changed

+72
-47
lines changed

src/static/js/Changeset.js

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,63 @@ class MergingOpAssembler {
436436
}
437437
}
438438

439+
/**
440+
* Canonicalizes a sequence of operations. Specifically:
441+
* - Skips no-op changes.
442+
* - Reorders consecutive '-' and '+' operations.
443+
* - Combines consecutive operations when possible.
444+
*
445+
* @param {Iterable<Op>} ops - Iterable of operations to combine.
446+
* @param {boolean} finalize - If truthy, omits the final op if it is an attributeless keep op.
447+
* @yields {Op} The canonicalized operations.
448+
* @returns {Generator<Op, number>} The done value indicates how much the sequence of operations
449+
* changes the length of the document (in characters).
450+
*/
451+
const canonicalizeOps = function* (ops, finalize) {
452+
let minusOps = [];
453+
let plusOps = [];
454+
let keepOps = [];
455+
let prevOpcode = '';
456+
let lengthChange = 0;
457+
458+
const flushPlusMinus = function* () {
459+
yield* squashOps(minusOps, false);
460+
minusOps = [];
461+
yield* squashOps(plusOps, false);
462+
plusOps = [];
463+
};
464+
465+
const flushKeeps = function* (finalize) {
466+
yield* squashOps(keepOps, finalize);
467+
keepOps = [];
468+
};
469+
470+
for (const op of ops) {
471+
if (!op.opcode || !op.chars) continue;
472+
switch (op.opcode) {
473+
case '-':
474+
if (prevOpcode === '=') yield* flushKeeps(false);
475+
minusOps.push(op);
476+
lengthChange -= op.chars;
477+
break;
478+
case '+':
479+
if (prevOpcode === '=') yield* flushKeeps(false);
480+
plusOps.push(op);
481+
lengthChange += op.chars;
482+
break;
483+
case '=':
484+
if (prevOpcode !== '=') yield* flushPlusMinus();
485+
keepOps.push(op);
486+
break;
487+
}
488+
prevOpcode = op.opcode;
489+
}
490+
491+
yield* flushPlusMinus();
492+
yield* flushKeeps(finalize);
493+
return lengthChange;
494+
};
495+
439496
/**
440497
* Generates operations from the given text and attributes.
441498
*
@@ -477,49 +534,19 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
477534
*/
478535
class SmartOpAssembler {
479536
constructor() {
480-
this._minusAssem = new MergingOpAssembler();
481-
this._plusAssem = new MergingOpAssembler();
482-
this._keepAssem = new MergingOpAssembler();
483-
this._assem = exports.stringAssembler();
484-
this._lastOpcode = '';
485-
this._lengthChange = 0;
486-
}
487-
488-
_flushKeeps() {
489-
this._assem.append(this._keepAssem.toString());
490-
this._keepAssem.clear();
537+
this.clear();
491538
}
492539

493-
_flushPlusMinus() {
494-
this._assem.append(this._minusAssem.toString());
495-
this._minusAssem.clear();
496-
this._assem.append(this._plusAssem.toString());
497-
this._plusAssem.clear();
540+
_serialize(finalize) {
541+
this._serialized = serializeOps((function* () {
542+
this._lengthChange = yield* canonicalizeOps(this._ops, finalize);
543+
}).call(this));
498544
}
499545

500546
append(op) {
501-
if (!op.opcode) return;
502-
if (!op.chars) return;
503-
504-
if (op.opcode === '-') {
505-
if (this._lastOpcode === '=') {
506-
this._flushKeeps();
507-
}
508-
this._minusAssem.append(op);
509-
this._lengthChange -= op.chars;
510-
} else if (op.opcode === '+') {
511-
if (this._lastOpcode === '=') {
512-
this._flushKeeps();
513-
}
514-
this._plusAssem.append(op);
515-
this._lengthChange += op.chars;
516-
} else if (op.opcode === '=') {
517-
if (this._lastOpcode !== '=') {
518-
this._flushPlusMinus();
519-
}
520-
this._keepAssem.append(op);
521-
}
522-
this._lastOpcode = op.opcode;
547+
this._serialized = null;
548+
this._lengthChange = null;
549+
this._ops.push(copyOp(op));
523550
}
524551

525552
/**
@@ -539,24 +566,22 @@ class SmartOpAssembler {
539566
}
540567

541568
toString() {
542-
this._flushPlusMinus();
543-
this._flushKeeps();
544-
return this._assem.toString();
569+
if (this._serialized == null) this._serialize(false);
570+
return this._serialized;
545571
}
546572

547573
clear() {
548-
this._minusAssem.clear();
549-
this._plusAssem.clear();
550-
this._keepAssem.clear();
551-
this._assem.clear();
552-
this._lengthChange = 0;
574+
this._ops = [];
575+
this._serialized = null;
576+
this._lengthChange = null;
553577
}
554578

555579
endDocument() {
556-
this._keepAssem.endDocument();
580+
this._serialize(true);
557581
}
558582

559583
getLengthChange() {
584+
if (this._lengthChange == null) this._serialize(false);
560585
return this._lengthChange;
561586
}
562587
}

0 commit comments

Comments
 (0)