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