import type { Selection } from '../public/mathfield' ; import { UndoStateChangeListener } from '../public/options' ; import type { ModelPrivate } from '../editor-model/model-private' ; import { Atom } from '../core/atom-class' ; import { ModeEditor } from '../editor-mathfield/mode-editor' ; export type UndoRecord = { latex : string ; selection : Selection ; } ; interface UndoOptions { onUndoStateWillChange ? : UndoStateChangeListener ; onUndoStateDidChange ? : UndoStateChangeListener ; suppressChangeNotifications ? : boolean ; } export class UndoManager { private model : ModelPrivate ; private get maximumDepth ( ) { return 1000 ; } private record = false ; private canCoalesce = false ; private stack : UndoRecord [ ] ; private index : number ; constructor ( model ) { this . model = model ; this . reset ( ) ; } reset ( ) { this . stack = [ ] ; this . index = - 1 ; } startRecording ( ) { this . record = true ; } canUndo ( ) { return this . index > 0 ; } canRedo ( ) { return this . index !== this . stack . length - 1 ; } undo ( options ) { if ( this . canUndo ( ) ) { if ( typeof options ?. onUndoStateWillChange === 'function' ) { options . onUndoStateWillChange ( this . model . mathfield , 'undo' ) ; } this . restore ( this . stack [ this . index - 1 ] , options ) ; this . index -= 1 ; if ( options && typeof options . onUndoStateDidChange === 'function' ) { options . onUndoStateDidChange ( this . model . mathfield , 'undo' ) ; } this . canCoalesce = false ; } } redo ( options ) { if ( this . canRedo ( ) ) { if ( typeof options ?. onUndoStateWillChange === 'function' ) { options . onUndoStateWillChange ( this . model . mathfield , 'redo' ) ; } this . index += 1 ; this . restore ( this . stack [ this . index ] , options ) ; if ( options && typeof options . onUndoStateDidChange === 'function' ) { options . onUndoStateDidChange ( this . model . mathfield , 'redo' ) ; } this . canCoalesce = false ; } } pop ( ) { if ( this . canUndo ( ) ) { this . index -= 1 ; this . stack . pop ( ) ; } } /**
     * Push a snapshot of the content and selection of the mathfield onto the
     * undo stack so that it can potentially be reverted to later.
     */ snapshot ( options ) { if ( ! this . record ) return ; if ( typeof options ?. onUndoStateWillChange === 'function' ) { options . onUndoStateWillChange ( this . model . mathfield , 'snapshot' ) ; } this . stack . splice ( this . index + 1 , this . stack . length - this . index - 1 ) ; this . stack . push ( { latex : Atom . serialize ( this . model . root , { expandMacro : false , defaultMode : this . model . mathfield . options . defaultMode , } ) , selection : this . model . selection , } ) ; this . index ++ ; if ( this . stack . length > this . maximumDepth ) { this . stack . shift ( ) ; } if ( options && typeof options . onUndoStateDidChange === 'function' ) { options . onUndoStateDidChange ( this . model . mathfield , 'snapshot' ) ; } this . canCoalesce = false ; } snapshotAndCoalesce ( options ) { if ( this . canCoalesce ) { this . pop ( ) ; } this . snapshot ( options ) ; this . canCoalesce = true ; } /**
     * Return an object capturing the state of the content and selection of the
     * mathfield. Pass this object to restore() to reset the value of the math
     * field to this saved value. This does not affect the undo stack.
     */ save ( ) { return { latex : Atom . serialize ( this . model . root , { expandMacro : false , defaultMode : this . model . mathfield . options . defaultMode , } ) , selection : this . model . selection , } ; } /**
     * Set the content and selection of the mathfield to a value previously
     * captured with save() or stored in the undo stack.
     * This does not affect the undo stack.
     */ restore ( state , options ) { const wasSuppressing = this . model . suppressChangeNotifications ; if ( options . suppressChangeNotifications !== undefined ) { this . model . suppressChangeNotifications = options . suppressChangeNotifications ; } ModeEditor . insert ( 'math' , this . model , state ? state . latex : '' , { ... options , format : 'latex' , insertionMode : 'replaceAll' , selectionMode : 'after' , smartFence : false , } ) ; this . model . selection = state ? state . selection : { ranges : [ [ 0 , 0 ] ] } ; this . model . suppressChangeNotifications = wasSuppressing ; } }