@@ -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 */
478535class 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