@@ -330,6 +330,63 @@ class OpAssembler {
330330 }
331331}
332332
333+ /**
334+ * Combines consecutive operations when possible. Also skips no-op changes.
335+ *
336+ * @param {Iterable<Op> } ops - Iterable of operations to combine.
337+ * @param {boolean } finalize - If truthy, omits the final op if it is an attributeless keep op.
338+ * @yields {Op} The squashed operations.
339+ * @returns {Generator<Op> }
340+ */
341+ const squashOps = function * ( ops , finalize ) {
342+ let prevOp = new Op ( ) ;
343+ // If we get, for example, insertions [xxx\n,yyy], those don't merge, but if we get
344+ // [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. This variable stores the length of yyy and
345+ // any other newline-less ops immediately after it.
346+ let prevOpAdditionalCharsAfterNewline = 0 ;
347+
348+ const flush = function * ( finalize ) {
349+ if ( ! prevOp . opcode ) return ;
350+ if ( finalize && prevOp . opcode === '=' && ! prevOp . attribs ) {
351+ // final merged keep, leave it implicit
352+ } else {
353+ yield prevOp ;
354+ if ( prevOpAdditionalCharsAfterNewline ) {
355+ const op = new Op ( prevOp . opcode ) ;
356+ op . chars = prevOpAdditionalCharsAfterNewline ;
357+ op . lines = 0 ;
358+ op . attribs = prevOp . attribs ;
359+ yield op ;
360+ prevOpAdditionalCharsAfterNewline = 0 ;
361+ }
362+ }
363+ prevOp = new Op ( ) ;
364+ } ;
365+
366+ for ( const op of ops ) {
367+ if ( ! op . opcode || op . chars <= 0 ) continue ;
368+ if ( prevOp . opcode === op . opcode && prevOp . attribs === op . attribs ) {
369+ if ( op . lines > 0 ) {
370+ // bufOp and additional chars are all mergeable into a multi-line op
371+ prevOp . chars += prevOpAdditionalCharsAfterNewline + op . chars ;
372+ prevOp . lines += op . lines ;
373+ prevOpAdditionalCharsAfterNewline = 0 ;
374+ } else if ( prevOp . lines === 0 ) {
375+ // both prevOp and op are in-line
376+ prevOp . chars += op . chars ;
377+ } else {
378+ // append in-line text to multi-line prevOp
379+ prevOpAdditionalCharsAfterNewline += op . chars ;
380+ }
381+ } else {
382+ yield * flush ( false ) ;
383+ prevOp = copyOp ( op ) ; // prevOp is mutated, so make a copy to protect op.
384+ }
385+ }
386+
387+ yield * flush ( finalize ) ;
388+ } ;
389+
333390/**
334391 * Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
335392 * pure "keeps". It does not re-order operations.
@@ -340,58 +397,26 @@ class MergingOpAssembler {
340397 }
341398
342399 clear ( ) {
343- this . _assem = [ ] ;
344- this . _bufOp = new Op ( ) ;
345- // If we get, for example, insertions [xxx\n,yyy], those don't merge, but if we get
346- // [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. This variable stores the length of yyy and
347- // any other newline-less ops immediately after it.
348- this . _bufOpAdditionalCharsAfterNewline = 0 ;
400+ this . _ops = [ ] ;
401+ this . _serialized = null ;
349402 }
350403
351- _flush ( isEndDocument ) {
352- if ( ! this . _bufOp . opcode ) return ;
353- if ( isEndDocument && this . _bufOp . opcode === '=' && ! this . _bufOp . attribs ) {
354- // final merged keep, leave it implicit
355- } else {
356- this . _assem . push ( copyOp ( this . _bufOp ) ) ;
357- if ( this . _bufOpAdditionalCharsAfterNewline ) {
358- this . _bufOp . chars = this . _bufOpAdditionalCharsAfterNewline ;
359- this . _bufOp . lines = 0 ;
360- this . _assem . push ( copyOp ( this . _bufOp ) ) ;
361- this . _bufOpAdditionalCharsAfterNewline = 0 ;
362- }
363- }
364- this . _bufOp . opcode = '' ;
404+ _serialize ( finalize ) {
405+ this . _serialized = exports . serializeOps ( squashOps ( this . _ops , finalize ) ) ;
365406 }
366407
367408 append ( op ) {
368- if ( op . chars <= 0 ) return ;
369- if ( this . _bufOp . opcode === op . opcode && this . _bufOp . attribs === op . attribs ) {
370- if ( op . lines > 0 ) {
371- // this._bufOp and additional chars are all mergeable into a multi-line op
372- this . _bufOp . chars += this . _bufOpAdditionalCharsAfterNewline + op . chars ;
373- this . _bufOp . lines += op . lines ;
374- this . _bufOpAdditionalCharsAfterNewline = 0 ;
375- } else if ( this . _bufOp . lines === 0 ) {
376- // both this._bufOp and op are in-line
377- this . _bufOp . chars += op . chars ;
378- } else {
379- // append in-line text to multi-line this._bufOp
380- this . _bufOpAdditionalCharsAfterNewline += op . chars ;
381- }
382- } else {
383- this . _flush ( ) ;
384- copyOp ( op , this . _bufOp ) ;
385- }
409+ this . _serialized = null ;
410+ this . _ops . push ( copyOp ( op ) ) ;
386411 }
387412
388413 endDocument ( ) {
389- this . _flush ( true ) ;
414+ this . _serialize ( true ) ;
390415 }
391416
392417 toString ( ) {
393- this . _flush ( ) ;
394- return exports . serializeOps ( this . _assem ) ;
418+ if ( this . _serialized == null ) this . _serialize ( false ) ;
419+ return this . _serialized ;
395420 }
396421}
397422
0 commit comments