import * as Char from "./char" ; import * as Crdt from "./crdt" ; const DELAY_BETWEEN_BATCHES_MS = 1000 ; export default class History { private history : Crdt . RemoteChange [ ] [ ] ; private future : Crdt . RemoteChange [ ] [ ] ; private shouldStoreInNewBatch : boolean ; private lastActionTimestamp : number ; constructor ( ) { this . shouldStoreInNewBatch = false ; this . history = [ ] ; this . future = [ ] ; this . lastActionTimestamp = 0 ; } public makeUndoChanges ( lamport ) { if ( this . history . length === 0 ) { return null ; } const undoChanges = this . history . pop ( ) ! . map ( change => this . invert ( change , lamport ) ) ; this . future . push ( undoChanges ) ; this . shouldStoreInNewBatch = true ; return undoChanges ; } public makeRedoChanges ( lamport ) { if ( this . future . length === 0 ) { return null ; } const redoChanges = this . future . pop ( ) ! . map ( change => this . invert ( change , lamport ) ) ; this . history . push ( redoChanges ) ; this . shouldStoreInNewBatch = true ; return redoChanges ; } public onChanges ( changes ) { const now = Date . now ( ) ; const newBatch = this . shouldCreateNewActionBatch ( now , changes ) ; if ( newBatch ) { this . history . push ( changes ) ; } else { changes . forEach ( change => { this . history [ this . history . length - 1 ] . push ( change ) ; } ) ; } this . shouldStoreInNewBatch = false ; this . lastActionTimestamp = now ; } public onCursorMove ( ) { this . shouldStoreInNewBatch = true ; } private invert ( change , lamport ) { const char = change [ 1 ] ; switch ( change [ 0 ] ) { case "add" : return [ "remove" , Char . create ( char . position , char . lamport , char . value ) ] ; case "remove" : return [ "add" , Char . create ( char . position , lamport , char . value ) ] ; } } private shouldCreateNewActionBatch ( now , changes ) { if ( this . shouldStoreInNewBatch ) { return true ; } if ( changes . length > 1 ) { return true ; } const change = changes [ 0 ] ; if ( now > this . lastActionTimestamp + DELAY_BETWEEN_BATCHES_MS ) { return true ; } if ( this . history . length === 0 ) { return true ; } const lastChanges = this . history [ this . history . length - 1 ] ; const additions = lastChanges . reduce ( ( sum , change ) => sum + ( change [ 0 ] === "add" ? 1 : 0 ) , 0 ) ; switch ( change [ 0 ] ) { case "add" : if ( additions >= 10 ) { return true ; } if ( " \t\n\r" . includes ( change [ 1 ] . value ) ) { return true ; } break ; case "remove" : if ( additions > 0 ) { return true ; } if ( lastChanges . length >= 10 ) { return true ; } break ; } return false ; } }