import { assertGet , isEmptyArray } from '@remirror/core' ; import type { Step , StepMap , Transform } from '@remirror/pm/transform' ; interface SpanConstructorProps { from : number ; to : number ; commit : number | undefined ; } export class Span { from : number ; to : number ; commit : number | undefined ; constructor ( props ) { const { from , to , commit } = props ; this . from = from ; this . to = to ; this . commit = commit ; } } interface CommitConstructorProps { message : string ; time : number ; steps : Step [ ] ; maps : StepMap [ ] ; hidden ? : boolean ; } export class Commit { message : string ; time : number ; steps : Step [ ] ; maps : StepMap [ ] ; hidden ? : boolean ; constructor ( props ) { const { message , time , steps , maps , hidden } = props ; this . message = message ; this . time = time ; this . steps = steps ; this . maps = maps ; this . hidden = hidden ; } } interface TrackStateConstructorProps { blameMap : Span [ ] ; commits : Commit [ ] ; uncommittedSteps : Step [ ] ; uncommittedMaps : StepMap [ ] ; } export class TrackState { blameMap : Span [ ] ; commits : Commit [ ] ; uncommittedSteps : Step [ ] ; uncommittedMaps : StepMap [ ] ; constructor ( props ) { const { blameMap , commits , uncommittedSteps , uncommittedMaps } = props ; this . blameMap = blameMap ; this . commits = commits ; this . uncommittedSteps = uncommittedSteps ; this . uncommittedMaps = uncommittedMaps ; } /**
     * Apply a transform to this state.
     */ applyTransform ( transform ) { const inverted = transform . steps . map ( ( step , index ) => step . invert ( assertGet ( transform . docs , index ) ) ) ; const newBlame = updateBlameMap ( { map : this . blameMap , transform , id : this . commits . length } ) ; return new TrackState ( { blameMap : newBlame , commits : this . commits , uncommittedSteps : [ ... this . uncommittedSteps , ... inverted ] , uncommittedMaps : [ ... this . uncommittedMaps , ... transform . mapping . maps ] , } ) ; } /**
     * When a transaction is marked as a commit, this is used to put any
     * uncommitted steps into a new commit.
     */ applyCommit ( message , time ) { if ( isEmptyArray ( this . uncommittedSteps ) ) { return this ; } const commit = new Commit ( { message , time , steps : this . uncommittedSteps , maps : this . uncommittedMaps , } ) ; return new TrackState ( { blameMap : this . blameMap , commits : [ ... this . commits , commit ] , uncommittedSteps : [ ] , uncommittedMaps : [ ] , } ) ; } } interface UpdateBlameMapProps { map : Span [ ] ; transform : Transform ; id : number ; } function updateBlameMap ( { map , transform , id } ) { const result = [ ] ; const mapping = transform . mapping ; for ( const span of map ) { const from = mapping . map ( span . from , 1 ) ; const to = mapping . map ( span . to , - 1 ) ; if ( from < to ) { result . push ( new Span ( { from , to , commit : span . commit } ) ) ; } } for ( const [ index , map ] of mapping . maps . entries ( ) ) { const after = mapping . slice ( index + 1 ) ; map . forEach ( ( _s , _e , start , end ) => { insertIntoBlameMap ( { map : result , from : after . map ( start , 1 ) , to : after . map ( end , - 1 ) , commit : id , } ) ; } ) ; } return result ; } interface InsertIntoBlameMapProps { map : Span [ ] ; from : number ; to : number ; commit : number ; } function insertIntoBlameMap ( props ) { let { from , to , map , commit } = props ; if ( from >= to ) { return ; } let pos = 0 ; let next ; for ( ; pos < map . length ; pos ++ ) { next = assertGet ( map , pos ) ; if ( next . commit === commit ) { if ( next . to >= from ) { break ; } continue ; } if ( next . to <= from ) { continue ; } if ( next . from < from ) { const left = new Span ( { from : next . from , to : from , commit : next . commit } ) ; if ( next . to > to ) { map . splice ( pos ++ , 0 , left ) ; } else { map [ pos ++ ] = left ; } } break ; } while ( ( next = map [ pos ] ) ) { if ( next . commit === commit ) { if ( next . from > to ) { break ; } from = Math . min ( from , next . from ) ; to = Math . max ( to , next . to ) ; map . splice ( pos , 1 ) ; continue ; } if ( next . from >= to ) { break ; } if ( next . to > to ) { map [ pos ] = new Span ( { from : to , to : next . to , commit : next . commit } ) ; break ; } map . splice ( pos , 1 ) ; } map . splice ( pos , 0 , new Span ( { from , to , commit } ) ) ; }