import * as vscode from 'vscode' ; import { CheckPointObject , CheckPointTreeItem } from './Interfaces/CheckPointInterfaces' ; import { join , } from 'path' ; import { diff_match_patch , patch_obj } from 'diff-match-patch' ; import * as dateFormat from 'dateformat' ; import { logger , initLogger } from './logger' ; param timestamp param index export class CheckPointTreeItemImpl implements CheckPointTreeItem { constructor ( public timestamp , public index ) { } } param patches param timestamps param current export class CheckPointObjectImpl implements CheckPointObject { constructor ( public patches , public timestamps , public current , public active ) { } } /**
     * Represents TreeDataProvider implementation
*/ export class CheckPointProvider implements vscode . TreeDataProvider < CheckPointTreeItem > { /**diff-patch-match object */ private dmp : diff_match_patch ; /*** Event to signal that an element or root has changed.*/ private _onDidChangeTreeData : vscode . EventEmitter < CheckPointTreeItem > ; onDidChangeTreeData : vscode . Event < CheckPointTreeItem > ; /** Global context object */ private checkPointContext : vscode . ExtensionContext ; /** Current active CheckPoint object */ private checkPointObject : CheckPointObject ; /** Interval to store file in patches array */ private interval : number ; /** Last saved value of the current file */ private lastSavedFile : string ; /** Set if save chekpoint has to be skipped */ private skipSave : boolean ; param index returns private closestCheckPoint = ( index ) => { return ( Math . floor ( index / this . interval ) * this . interval ) ; } ; param context param currentFileCheckPointObject constructor ( context , currentFileCheckPointObject ) { if ( ! logger ) { initLogger ( context . logPath ) ; } this . _onDidChangeTreeData = new vscode . EventEmitter < CheckPointTreeItem > ( ) ; this . onDidChangeTreeData = this . _onDidChangeTreeData . event ; this . checkPointContext = context ; this . checkPointObject = currentFileCheckPointObject ; this . interval = vscode . workspace . getConfiguration ( "checkpoint" ) . get ( "interval" ) || 4 ; logger . info ( "Checkpoint interval has been set to: " + this . interval ) ; logger . info ( "Initialising diff patch match object" ) ; this . lastSavedFile = vscode . window . activeTextEditor ?. document . getText ( ) || "" ; this . dmp = new diff_match_patch ( ) ; this . skipSave = false ; } param checkPointData private editorUpdate ( checkPointData ) { return new Promise ( ( resolve , reject ) => { const { activeTextEditor } = vscode . window ; if ( activeTextEditor ) { const { document } = activeTextEditor ; if ( document ) { let dataRange = document . validateRange ( new vscode . Range ( 0 , 0 , document . lineCount as number , 0 ) ) ; activeTextEditor . edit ( editBuilder => { editBuilder . replace ( dataRange , checkPointData ) ; } ) . then ( ( ) => { this . lastSavedFile = document . getText ( ) ; logger . info ( "Active document text changed" ) ; resolve ( true ) ; } , ( ) => { logger . warn ( "Text editor update failed for: " + document . fileName ) ; reject ( false ) ; } ) ; } else { logger . warn ( "Document is undefined" ) ; reject ( false ) ; } } else { logger . warn ( "Active editor is undefined" ) ; reject ( false ) ; } } ) ; } param index public openCheckPoint ( index ) { logger . info ( "Checkpoint " + index + " selected" ) ; const document = vscode . window . activeTextEditor ?. document ; if ( document ) { if ( this . lastSavedFile !== document ?. getText ( ) ) { this . saveCheckPoint ( document as vscode . TextDocument ) ; } } else { logger . warn ( "Document is undefined" ) ; } return new Promise ( async ( resolve , reject ) => { const file = await this . generateFileByPatch ( index ) . catch ( error => { reject ( error ) ; return "" ; } ) ; await this . editorUpdate ( file ) . catch ( error => { logger . warn ( "Editor update failed for index: " + index ) ; reject ( error ) ; } ) ; resolve ( true ) ; } ) ; } param document param calledByOpen public async saveCheckPoint ( document ) { if ( ! document ) { logger . warn ( "Document is undefined" ) ; return ; } const currentFile = document . getText ( ) ; let previousFile = this . checkPointObject . current ; if ( this . skipSave || ( vscode . window . activeTextEditor ?. document . fileName !== document . fileName ) || ( currentFile === previousFile ) ) { logger . info ( document . fileName + " not saved because it is either:\n 1.Active,\t2.Saved As,\t3.Same file" ) ; return ; } logger . info ( "Saving new checkpoint for: " + document . fileName ) ; this . lastSavedFile = currentFile ; if ( this . checkPointObject . patches . length % this . interval === 0 ) { logger . info ( "Saving file" ) ; this . checkPointObject . patches . push ( currentFile ) ; } else { logger . info ( "Saving patch" ) ; let previousFile = this . checkPointObject . current ; let patch = this . dmp . patch_make ( previousFile , currentFile ) ; this . checkPointObject . patches . push ( patch ) ; } this . checkPointObject . current = currentFile ; this . checkPointObject . timestamps . push ( new Date ( Date . now ( ) ) ) ; this . checkPointObject . active = this . checkPointObject . patches . length - 1 ; logger . info ( "Checkpoint " + this . checkPointObject . active + " created for: " + document . fileName ) ; await this . updateCheckPointObject ( this . checkPointObject ) . catch ( error => logger . warn ( "Failed to update checkPointObject" ) ) ; } param element return async getChildren ( element ? ) { let result = [ ] ; if ( Object . keys ( this . checkPointObject ) . length === 0 ) { logger . info ( "No tree data" ) ; return result ; } else { logger . info ( "Creating tree items" ) ; for ( let i = 0 ; i < this . checkPointObject . patches . length ; i ++ ) { const treeItem = new CheckPointTreeItemImpl ( this . checkPointObject . timestamps [ i ] , i ) ; result . push ( treeItem ) ; } } return result ; } param element return getTreeItem ( element ) { const treeItem = new vscode . TreeItem ( dateFormat ( element . timestamp ) ) ; let resourcePath = join ( __dirname , '..' , 'resources' , element . index === this . checkPointObject . active ? '/active.svg' : '/normal.svg' ) ; treeItem . contextValue = "checkPointItem" ; treeItem . iconPath = resourcePath ; treeItem . collapsibleState = vscode . TreeItemCollapsibleState . None ; treeItem . command = { command : 'checkPointExplorer.openCheckPoint' , title : "Open CheckPoint" , arguments : [ element . index ] , } ; return treeItem ; } param checkPointObject returns updateCheckPointObject ( checkPointObject ) { return new Promise ( ( resolve , reject ) => { const document = vscode . window . activeTextEditor ?. document ; const fileName = document ?. fileName ; if ( fileName ) { if ( Object . keys ( checkPointObject ) . length === 0 ) { logger . info ( "Clearing all checkpoint data for: " + fileName ) ; this . checkPointContext . globalState . update ( fileName , null ) ; } else { logger . info ( "Updated checkpoint data for: " + fileName ) ; this . checkPointContext . globalState . update ( fileName , checkPointObject ) ; } this . checkPointObject = checkPointObject ; this . lastSavedFile = document ?. getText ( ) || "" ; logger . info ( "Refreshing tree" ) ; this . _onDidChangeTreeData . fire ( ) ; resolve ( true ) ; } else { logger . warn ( "Active editor is undefined" ) ; this . checkPointObject = checkPointObject ; this . _onDidChangeTreeData . fire ( ) ; reject ( false ) ; } } ) ; } param string returns updateLastSavedFile ( documentText ) { this . lastSavedFile = documentText ; } param index returns deleteSingleCheckPoint ( index ) { return new Promise ( async ( resolve , reject ) => { let generatedFile = "" ; logger . info ( "Deleing checkpoint: " + index ) ; if ( this . checkPointObject . patches . length === 1 && index === 0 ) { await this . updateCheckPointObject ( { } as CheckPointObject ) . catch ( error => reject ( false ) ) ; } else { if ( index === this . checkPointObject . patches . length - 1 ) { this . checkPointObject . current = await this . generateFileByPatch ( index - 1 ) . catch ( error => Promise . reject ( false ) ) ; } else if ( index === this . closestCheckPoint ( index ) ) { generatedFile = this . checkPointObject . patches [ index ] as string ; } else { generatedFile = await this . generateFileByPatch ( index - 1 ) . catch ( error => Promise . reject ( false ) ) ; this . checkPointObject . patches [ index + 1 ] = this . dmp . patch_make ( generatedFile , await this . generateFileByPatch ( index + 1 ) . catch ( error => Promise . reject ( false ) ) ) ; } this . checkPointObject . patches . splice ( index , 1 ) ; this . checkPointObject . timestamps . splice ( index , 1 ) ; for ( let k = index ; k < this . checkPointObject . patches . length ; k ++ ) { if ( k === this . closestCheckPoint ( k ) ) { this . checkPointObject . patches [ k ] = this . dmp . patch_apply ( this . checkPointObject . patches [ k ] as patch_obj [ ] , generatedFile ) [ 0 ] ; } else if ( k === this . closestCheckPoint ( k + 1 ) - 1 ) { this . checkPointObject . patches [ k ] = this . dmp . patch_make ( generatedFile , this . checkPointObject . patches [ k ] as string ) ; } generatedFile = await this . generateFileByPatch ( k ) . catch ( error => Promise . reject ( false ) ) ; } if ( index === this . checkPointObject . active ) { await this . setActiveCheckPoint ( index === this . checkPointObject . patches . length ? index - 1 : index ) . catch ( error => reject ( error ) ) ; } else { if ( index < this . checkPointObject . active ) { this . checkPointObject . active -= 1 ; } await this . updateCheckPointObject ( this . checkPointObject ) . catch ( error => reject ( error ) ) ; } } resolve ( this . checkPointObject ) ; } ) ; } param index returns private generateFileByPatch ( index ) { return new Promise ( ( resolve , reject ) => { if ( this . checkPointObject . patches . length >= index ) { logger . info ( "Generating file for checkpoint: " + index ) ; let generatedFile = this . checkPointObject . patches [ this . closestCheckPoint ( index ) ] as string ; for ( let i = this . closestCheckPoint ( index ) + 1 ; i <= index ; i ++ ) { generatedFile = this . dmp . patch_apply ( this . checkPointObject . patches [ i ] as patch_obj [ ] , generatedFile ) [ 0 ] ; } resolve ( generatedFile ) ; } else { logger . warn ( "File generation failed at index: " + index ) ; reject ( false ) ; } } ) ; } param index setActiveCheckPoint ( index ) { return new Promise ( async ( resolve , reject ) => { await this . openCheckPoint ( index ) . catch ( error => reject ( error ) ) ; this . skipSave = true ; const document = vscode . window . activeTextEditor ?. document ; if ( document ) { this . checkPointObject . active = index ; logger . info ( "Active checkpoint set to: " + index ) ; await vscode . window . activeTextEditor ?. document . save ( ) ; await this . updateCheckPointObject ( this . checkPointObject ) . catch ( error => reject ( false ) ) ; this . skipSave = false ; resolve ( true ) ; } else { logger . warn ( "Document is undefined" ) ; reject ( false ) ; } } ) ; } }