Skip to content

Commit 74bed6a

Browse files
committed
Changeset: Use a generator to implement SmartOpAssembler
Eventually all uses of the class will be switched to the generator.
1 parent 2a2f15b commit 74bed6a

File tree

1 file changed

+71
-44
lines changed

1 file changed

+71
-44
lines changed

src/static/js/Changeset.js

Lines changed: 71 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,63 @@ class MergingOpAssembler {
420420
}
421421
}
422422

423+
/**
424+
* Canonicalizes a sequence of operations. Specifically:
425+
* - Skips no-op changes.
426+
* - Reorders consecutive '-' and '+' operations.
427+
* - Combines consecutive operations when possible.
428+
*
429+
* @param {Iterable<Op>} ops - Iterable of operations to combine.
430+
* @param {boolean} finalize - If truthy, omits the final op if it is an attributeless keep op.
431+
* @yields {Op} The canonicalized operations.
432+
* @returns {Generator<Op, number>} The done value indicates how much the sequence of operations
433+
* changes the length of the document (in characters).
434+
*/
435+
const canonicalizeOps = function* (ops, finalize) {
436+
let minusOps = [];
437+
let plusOps = [];
438+
let keepOps = [];
439+
let prevOpcode = '';
440+
let lengthChange = 0;
441+
442+
const flushPlusMinus = function* () {
443+
yield* exports.squashOps(minusOps, false);
444+
minusOps = [];
445+
yield* exports.squashOps(plusOps, false);
446+
plusOps = [];
447+
};
448+
449+
const flushKeeps = function* (finalize) {
450+
yield* exports.squashOps(keepOps, finalize);
451+
keepOps = [];
452+
};
453+
454+
for (const op of ops) {
455+
if (!op.opcode || !op.chars) continue;
456+
switch (op.opcode) {
457+
case '-':
458+
if (prevOpcode === '=') yield* flushKeeps(false);
459+
minusOps.push(op);
460+
lengthChange -= op.chars;
461+
break;
462+
case '+':
463+
if (prevOpcode === '=') yield* flushKeeps(false);
464+
plusOps.push(op);
465+
lengthChange += op.chars;
466+
break;
467+
case '=':
468+
if (prevOpcode !== '=') yield* flushPlusMinus();
469+
keepOps.push(op);
470+
break;
471+
}
472+
prevOpcode = op.opcode;
473+
}
474+
475+
yield* flushPlusMinus();
476+
yield* flushKeeps(finalize);
477+
return lengthChange;
478+
};
479+
423480
/**
424481
* Generates operations from the given text and attributes.
425482
*
@@ -465,54 +522,25 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
465522
*/
466523
class SmartOpAssembler {
467524
constructor() {
468-
this._assem = exports.stringAssembler();
469525
this.clear();
470526
}
471527

472528
clear() {
473-
this._minusAssem = [];
474-
this._plusAssem = [];
475-
this._keepAssem = [];
476-
this._assem.clear();
477-
this._lastOpcode = '';
478-
this._lengthChange = 0;
479-
}
480-
481-
_flushKeeps(finalize) {
482-
this._assem.append(exports.serializeOps(exports.squashOps(this._keepAssem, finalize)));
483-
this._keepAssem = [];
529+
this._ops = [];
530+
this._serialized = null;
531+
this._lengthChange = null;
484532
}
485533

486-
_flushPlusMinus() {
487-
this._assem.append(exports.serializeOps(exports.squashOps(this._minusAssem, false)));
488-
this._minusAssem = [];
489-
this._assem.append(exports.serializeOps(exports.squashOps(this._plusAssem, false)));
490-
this._plusAssem = [];
534+
_serialize(finalize) {
535+
this._serialized = exports.serializeOps((function* () {
536+
this._lengthChange = yield* canonicalizeOps(this._ops, finalize);
537+
}).call(this));
491538
}
492539

493540
append(op) {
494-
if (!op.opcode) return;
495-
if (!op.chars) return;
496-
497-
if (op.opcode === '-') {
498-
if (this._lastOpcode === '=') {
499-
this._flushKeeps(false);
500-
}
501-
this._minusAssem.push(copyOp(op));
502-
this._lengthChange -= op.chars;
503-
} else if (op.opcode === '+') {
504-
if (this._lastOpcode === '=') {
505-
this._flushKeeps(false);
506-
}
507-
this._plusAssem.push(copyOp(op));
508-
this._lengthChange += op.chars;
509-
} else if (op.opcode === '=') {
510-
if (this._lastOpcode !== '=') {
511-
this._flushPlusMinus();
512-
}
513-
this._keepAssem.push(copyOp(op));
514-
}
515-
this._lastOpcode = op.opcode;
541+
this._serialized = null;
542+
this._lengthChange = null;
543+
this._ops.push(copyOp(op));
516544
}
517545

518546
/**
@@ -533,17 +561,16 @@ class SmartOpAssembler {
533561
}
534562

535563
toString() {
536-
this._flushPlusMinus();
537-
this._flushKeeps(false);
538-
return this._assem.toString();
564+
if (this._serialized == null) this._serialize(false);
565+
return this._serialized;
539566
}
540567

541568
endDocument() {
542-
this._flushPlusMinus();
543-
this._flushKeeps(true);
569+
this._serialize(true);
544570
}
545571

546572
getLengthChange() {
573+
if (this._lengthChange == null) this._serialize(false);
547574
return this._lengthChange;
548575
}
549576
}

0 commit comments

Comments
 (0)