-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix save on close #13567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix save on close #13567
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix saving during close and auto backup to actually save a notebook. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -108,7 +108,8 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit | |
@inject(IConfigurationService) protected readonly configuration: IConfigurationService, | ||
@inject(ICustomEditorService) private customEditorService: ICustomEditorService, | ||
@inject(INotebookStorageProvider) protected readonly storage: INotebookStorageProvider, | ||
@inject(INotebookProvider) private readonly notebookProvider: INotebookProvider | ||
@inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, | ||
@inject(IDataScienceFileSystem) protected readonly fs: IDataScienceFileSystem | ||
) { | ||
traceInfo(`id is ${this._id}`); | ||
|
||
|
@@ -214,14 +215,18 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit | |
public async loadModel(file: Uri, contents?: string, backupId?: string): Promise<INotebookModel>; | ||
// tslint:disable-next-line: no-any | ||
public async loadModel(file: Uri, contents?: string, options?: any): Promise<INotebookModel> { | ||
// Every time we load a new untitled file, up the counter past the max value for this counter | ||
this.untitledCounter = getNextUntitledCounter(file, this.untitledCounter); | ||
// Get the model that may match this file | ||
let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, file)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was necessary for custom editor to find the correct model. |
||
if (!model) { | ||
// Every time we load a new untitled file, up the counter past the max value for this counter | ||
this.untitledCounter = getNextUntitledCounter(file, this.untitledCounter); | ||
|
||
// Load our model from our storage object. | ||
const model = await this.storage.getOrCreateModel(file, contents, options); | ||
// Load our model from our storage object. | ||
model = await this.storage.getOrCreateModel(file, contents, options); | ||
|
||
// Make sure to listen to events on the model | ||
this.trackModel(model); | ||
// Make sure to listen to events on the model | ||
this.trackModel(model); | ||
} | ||
return model; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ import { | |
import detectIndent = require('detect-indent'); | ||
import { VSCodeNotebookModel } from './vscNotebookModel'; | ||
|
||
const KeyPrefix = 'notebook-storage-'; | ||
export const KeyPrefix = 'notebook-storage-'; | ||
const NotebookTransferKey = 'notebook-transfered'; | ||
|
||
export function getNextUntitledCounter(file: Uri | undefined, currentValue: number): number { | ||
|
@@ -178,21 +178,29 @@ export class NativeEditorStorage implements INotebookStorage { | |
// Keep track of the time when this data was saved. | ||
// This way when we retrieve the data we can compare it against last modified date of the file. | ||
const specialContents = contents ? JSON.stringify({ contents, lastModifiedTimeMs: Date.now() }) : undefined; | ||
return this.writeToStorage(filePath, specialContents, cancelToken); | ||
return this.writeToStorage(model.file, filePath, specialContents, cancelToken); | ||
} | ||
|
||
private async clearHotExit(file: Uri, backupId?: string): Promise<void> { | ||
const key = backupId || this.getStaticStorageKey(file); | ||
const filePath = this.getHashedFileName(key); | ||
await this.writeToStorage(filePath); | ||
await this.writeToStorage(undefined, filePath); | ||
} | ||
|
||
private async writeToStorage(filePath: string, contents?: string, cancelToken?: CancellationToken): Promise<void> { | ||
private async writeToStorage( | ||
owningFile: Uri | undefined, | ||
filePath: string, | ||
contents?: string, | ||
cancelToken?: CancellationToken | ||
): Promise<void> { | ||
try { | ||
if (!cancelToken?.isCancellationRequested) { | ||
if (contents) { | ||
await this.fs.createLocalDirectory(path.dirname(filePath)); | ||
if (!cancelToken?.isCancellationRequested) { | ||
if (owningFile) { | ||
this.trustService.trustNotebook(owningFile, contents).ignoreErrors(); | ||
} | ||
await this.fs.writeLocalFile(filePath, contents); | ||
} | ||
} else { | ||
|
@@ -374,6 +382,8 @@ export class NativeEditorStorage implements INotebookStorage { | |
if (isNotebookTrusted) { | ||
model.trust(); | ||
} | ||
} else { | ||
model.trust(); | ||
} | ||
|
||
return model; | ||
|
@@ -407,9 +417,10 @@ export class NativeEditorStorage implements INotebookStorage { | |
} | ||
|
||
private async getStoredContentsFromFile(file: Uri, key: string): Promise<string | undefined> { | ||
const filePath = this.getHashedFileName(key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line was the removed one. I believe it's the root cause of the problem. |
||
try { | ||
// Use this to read from the extension global location | ||
const contents = await this.fs.readLocalFile(file.fsPath); | ||
const contents = await this.fs.readLocalFile(filePath); | ||
const data = JSON.parse(contents); | ||
// Check whether the file has been modified since the last time the contents were saved. | ||
if (data && data.lastModifiedTimeMs && file.scheme === 'file') { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import { forceLoad } from '../transforms'; | |
import { isAllowedAction, isAllowedMessage, postActionToExtension } from './helpers'; | ||
import { generatePostOfficeSendReducer } from './postOffice'; | ||
import { generateMonacoReducer, IMonacoState } from './reducers/monaco'; | ||
import { CommonActionType } from './reducers/types'; | ||
import { generateVariableReducer, IVariableState } from './reducers/variables'; | ||
|
||
function generateDefaultState( | ||
|
@@ -109,19 +110,21 @@ function createSendInfoMiddleware(): Redux.Middleware<{}, IStore> { | |
} | ||
|
||
// If cell vm count changed or selected cell changed, send the message | ||
const currentSelection = getSelectedAndFocusedInfo(afterState.main); | ||
if ( | ||
prevState.main.cellVMs.length !== afterState.main.cellVMs.length || | ||
getSelectedAndFocusedInfo(prevState.main).selectedCellId !== currentSelection.selectedCellId || | ||
prevState.main.undoStack.length !== afterState.main.undoStack.length || | ||
prevState.main.redoStack.length !== afterState.main.redoStack.length | ||
) { | ||
postActionToExtension({ queueAction: store.dispatch }, InteractiveWindowMessages.SendInfo, { | ||
cellCount: afterState.main.cellVMs.length, | ||
undoCount: afterState.main.undoStack.length, | ||
redoCount: afterState.main.redoStack.length, | ||
selectedCell: currentSelection.selectedCellId | ||
}); | ||
if (!action.type || action.type !== CommonActionType.UNMOUNT) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was this change for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was necessary to add the check for the mounted web view being used by dispose. We were sending messages after the webview had been destroyed. |
||
const currentSelection = getSelectedAndFocusedInfo(afterState.main); | ||
if ( | ||
prevState.main.cellVMs.length !== afterState.main.cellVMs.length || | ||
getSelectedAndFocusedInfo(prevState.main).selectedCellId !== currentSelection.selectedCellId || | ||
prevState.main.undoStack.length !== afterState.main.undoStack.length || | ||
prevState.main.redoStack.length !== afterState.main.redoStack.length | ||
) { | ||
postActionToExtension({ queueAction: store.dispatch }, InteractiveWindowMessages.SendInfo, { | ||
cellCount: afterState.main.cellVMs.length, | ||
undoCount: afterState.main.undoStack.length, | ||
redoCount: afterState.main.redoStack.length, | ||
selectedCell: currentSelection.selectedCellId | ||
}); | ||
} | ||
} | ||
return res; | ||
}; | ||
|
@@ -159,21 +162,26 @@ function createTestMiddleware(): Redux.Middleware<{}, IStore> { | |
}); | ||
}; | ||
|
||
// Special case for focusing a cell | ||
const previousSelection = getSelectedAndFocusedInfo(prevState.main); | ||
const currentSelection = getSelectedAndFocusedInfo(afterState.main); | ||
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && currentSelection.focusedCellId) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.FocusedCellEditor, { cellId: action.payload.cellId }); | ||
} | ||
if (previousSelection.selectedCellId !== currentSelection.selectedCellId && currentSelection.selectedCellId) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.SelectedCell, { cellId: action.payload.cellId }); | ||
} | ||
// Special case for unfocusing a cell | ||
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && !currentSelection.focusedCellId) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.UnfocusedCellEditor); | ||
if (!action.type || action.type !== CommonActionType.UNMOUNT) { | ||
// Special case for focusing a cell | ||
const previousSelection = getSelectedAndFocusedInfo(prevState.main); | ||
const currentSelection = getSelectedAndFocusedInfo(afterState.main); | ||
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && currentSelection.focusedCellId) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.FocusedCellEditor, { cellId: action.payload.cellId }); | ||
} | ||
if ( | ||
previousSelection.selectedCellId !== currentSelection.selectedCellId && | ||
currentSelection.selectedCellId | ||
) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.SelectedCell, { cellId: action.payload.cellId }); | ||
} | ||
// Special case for unfocusing a cell | ||
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && !currentSelection.focusedCellId) { | ||
// Send async so happens after render state changes (so our enzyme wrapper is up to date) | ||
sendMessage(InteractiveWindowMessages.UnfocusedCellEditor); | ||
} | ||
} | ||
|
||
// Indicate settings updates | ||
|
@@ -218,7 +226,10 @@ function createTestMiddleware(): Redux.Middleware<{}, IStore> { | |
sendMessage(InteractiveWindowMessages.ExecutionRendered); | ||
} | ||
|
||
if (!action.type || action.type !== InteractiveWindowMessages.FinishCell) { | ||
if ( | ||
!action.type || | ||
(action.type !== InteractiveWindowMessages.FinishCell && action.type !== CommonActionType.UNMOUNT) | ||
) { | ||
// Might be a non finish but still update cells (like an undo or a delete) | ||
const prevFinished = prevState.main.cellVMs | ||
.filter((c) => c.cell.state === CellState.finished || c.cell.state === CellState.error) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sure the correct model is used for saving.