@@ -42,7 +42,27 @@ export function isUntitled(model?: INotebookModel): boolean {
4242 return isUntitledFile ( model ?. file ) ;
4343}
4444
45- class NativeEditorNotebookModel implements INotebookModel {
45+ export function getNextUntitledCounter ( file : Uri | undefined , currentValue : number ) : number {
46+ if ( file && isUntitledFile ( file ) ) {
47+ const basename = path . basename ( file . fsPath , 'ipynb' ) ;
48+ const extname = path . extname ( file . fsPath ) ;
49+ if ( extname . toLowerCase ( ) === '.ipynb' ) {
50+ // See if ends with -<n>
51+ const match = / .* - ( \d + ) / . exec ( basename ) ;
52+ if ( match && match [ 1 ] ) {
53+ const fileValue = parseInt ( match [ 1 ] , 10 ) ;
54+ if ( fileValue ) {
55+ return Math . max ( currentValue , fileValue + 1 ) ;
56+ }
57+ }
58+ }
59+ }
60+
61+ return currentValue ;
62+ }
63+
64+ // Exported for test mocks
65+ export class NativeEditorNotebookModel implements INotebookModel {
4666 public get onDidDispose ( ) {
4767 return this . _disposed . event ;
4868 }
@@ -71,6 +91,9 @@ class NativeEditorNotebookModel implements INotebookModel {
7191 public get metadata ( ) : nbformat . INotebookMetadata | undefined {
7292 return this . _state . notebookJson . metadata ;
7393 }
94+ public get id ( ) {
95+ return this . _id ;
96+ }
7497 private _disposed = new EventEmitter < void > ( ) ;
7598 private _isDisposed ?: boolean ;
7699 private _changedEmitter = new EventEmitter < NotebookModelChange > ( ) ;
@@ -83,6 +106,8 @@ class NativeEditorNotebookModel implements INotebookModel {
83106 notebookJson : { }
84107 } ;
85108
109+ private _id = uuid ( ) ;
110+
86111 constructor (
87112 file : Uri ,
88113 cells : ICell [ ] ,
@@ -148,7 +173,7 @@ class NativeEditorNotebookModel implements INotebookModel {
148173
149174 // Forward onto our listeners if necessary
150175 if ( changed || this . isDirty !== oldDirty ) {
151- this . _changedEmitter . fire ( { ...change , newDirty : this . isDirty , oldDirty } ) ;
176+ this . _changedEmitter . fire ( { ...change , newDirty : this . isDirty , oldDirty, model : this } ) ;
152177 }
153178 // Slightly different for the event we send to VS code. Skip version and file changes. Only send user events.
154179 if ( ( changed || this . isDirty !== oldDirty ) && change . kind !== 'version' && change . source === 'user' ) {
@@ -461,8 +486,8 @@ export class NativeEditorStorage implements INotebookStorage {
461486 return isUntitledFile ( file ) ;
462487 }
463488
464- public async load ( file : Uri , possibleContents ?: string ) : Promise < INotebookModel > {
465- return this . loadFromFile ( file , possibleContents ) ;
489+ public async load ( file : Uri , possibleContents ?: string , skipDirtyContents ?: boolean ) : Promise < INotebookModel > {
490+ return this . loadFromFile ( file , possibleContents , skipDirtyContents ) ;
466491 }
467492 public async save ( model : INotebookModel , _cancellation : CancellationToken ) : Promise < void > {
468493 const contents = model . getContent ( ) ;
@@ -473,6 +498,7 @@ export class NativeEditorStorage implements INotebookStorage {
473498 oldDirty : model . isDirty ,
474499 newDirty : false
475500 } ) ;
501+ this . clearHotExit ( model . file ) . ignoreErrors ( ) ;
476502 }
477503
478504 public async saveAs ( model : INotebookModel , file : Uri ) : Promise < void > {
@@ -487,6 +513,7 @@ export class NativeEditorStorage implements INotebookStorage {
487513 target : file
488514 } ) ;
489515 this . savedAs . fire ( { new : file , old } ) ;
516+ this . clearHotExit ( old ) . ignoreErrors ( ) ;
490517 }
491518 public async backup ( model : INotebookModel , cancellation : CancellationToken ) : Promise < void > {
492519 // Should send to extension context storage path
@@ -508,16 +535,23 @@ export class NativeEditorStorage implements INotebookStorage {
508535
509536 return this . writeToStorage ( filePath , specialContents , cancelToken ) ;
510537 }
538+
539+ private async clearHotExit ( file : Uri ) : Promise < void > {
540+ const key = this . getStorageKey ( file ) ;
541+ const filePath = this . getHashedFileName ( key ) ;
542+ return this . writeToStorage ( filePath , undefined ) ;
543+ }
544+
511545 private async writeToStorage ( filePath : string , contents ?: string , cancelToken ?: CancellationToken ) : Promise < void > {
512546 try {
513547 if ( ! cancelToken ?. isCancellationRequested ) {
514548 if ( contents ) {
515549 await this . fileSystem . createDirectory ( path . dirname ( filePath ) ) ;
516550 if ( ! cancelToken ?. isCancellationRequested ) {
517- return this . fileSystem . writeFile ( filePath , contents ) ;
551+ return await this . fileSystem . writeFile ( filePath , contents ) ;
518552 }
519- } else {
520- return this . fileSystem . deleteFile ( filePath ) ;
553+ } else if ( await this . fileSystem . fileExists ( filePath ) ) {
554+ return await this . fileSystem . deleteFile ( filePath ) ;
521555 }
522556 }
523557 } catch ( exc ) {
@@ -561,15 +595,24 @@ export class NativeEditorStorage implements INotebookStorage {
561595 noop ( ) ;
562596 }
563597 }
564- private async loadFromFile ( file : Uri , possibleContents ?: string ) : Promise < INotebookModel > {
598+ private async loadFromFile (
599+ file : Uri ,
600+ possibleContents ?: string ,
601+ skipDirtyContents ?: boolean
602+ ) : Promise < INotebookModel > {
565603 try {
566604 // Attempt to read the contents if a viable file
567605 const contents = NativeEditorStorage . isUntitledFile ( file )
568606 ? possibleContents
569607 : await this . fileSystem . readFile ( file . fsPath ) ;
570608
609+ // If skipping dirty contents, delete the dirty hot exit file now
610+ if ( skipDirtyContents ) {
611+ await this . clearHotExit ( file ) ;
612+ }
613+
571614 // See if this file was stored in storage prior to shutdown
572- const dirtyContents = await this . getStoredContents ( file ) ;
615+ const dirtyContents = skipDirtyContents ? undefined : await this . getStoredContents ( file ) ;
573616 if ( dirtyContents ) {
574617 // This means we're dirty. Indicate dirty and load from this content
575618 return this . loadContents ( file , dirtyContents , true ) ;
@@ -668,18 +711,13 @@ export class NativeEditorStorage implements INotebookStorage {
668711 const contents = await this . fileSystem . readFile ( filePath ) ;
669712 const data = JSON . parse ( contents ) ;
670713 // Check whether the file has been modified since the last time the contents were saved.
671- if (
672- data &&
673- data . lastModifiedTimeMs &&
674- ! NativeEditorStorage . isUntitledFile ( file ) &&
675- file . scheme === 'file'
676- ) {
714+ if ( data && data . lastModifiedTimeMs && file . scheme === 'file' ) {
677715 const stat = await this . fileSystem . stat ( file . fsPath ) ;
678716 if ( stat . mtime > data . lastModifiedTimeMs ) {
679717 return ;
680718 }
681719 }
682- if ( data && ! NativeEditorStorage . isUntitledFile ( file ) && data . contents ) {
720+ if ( data && data . contents ) {
683721 return data . contents ;
684722 }
685723 } catch ( exc ) {
@@ -700,28 +738,23 @@ export class NativeEditorStorage implements INotebookStorage {
700738 }
701739
702740 // Check whether the file has been modified since the last time the contents were saved.
703- if (
704- data &&
705- data . lastModifiedTimeMs &&
706- ! NativeEditorStorage . isUntitledFile ( file ) &&
707- file . scheme === 'file'
708- ) {
741+ if ( data && data . lastModifiedTimeMs && file . scheme === 'file' ) {
709742 const stat = await this . fileSystem . stat ( file . fsPath ) ;
710743 if ( stat . mtime > data . lastModifiedTimeMs ) {
711744 return ;
712745 }
713746 }
714- if ( data && ! NativeEditorStorage . isUntitledFile ( file ) && data . contents ) {
747+ if ( data && data . contents ) {
715748 return data . contents ;
716749 }
717750 } catch {
718751 noop ( ) ;
719752 }
720753 }
721754
722- private async getStoredContentsFromLocalStorage ( file : Uri , key : string ) : Promise < string | undefined > {
755+ private async getStoredContentsFromLocalStorage ( _file : Uri , key : string ) : Promise < string | undefined > {
723756 const workspaceData = this . localStorage . get < string > ( key ) ;
724- if ( workspaceData && ! NativeEditorStorage . isUntitledFile ( file ) ) {
757+ if ( workspaceData ) {
725758 // Make sure to clear so we don't use this again.
726759 this . localStorage . update ( key , undefined ) ;
727760
0 commit comments