Skip to content

Commit

Permalink
allow returning undefined if file was not saved (microsoft#213123)
Browse files Browse the repository at this point in the history
* allow returning undefined if file was not saved

* bring back cancellation check

* helper utility to convey optional result if cancelled

* edit

* inline function

* test cancelled custom save

* inline more

---------

Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
  • Loading branch information
2 people authored and andremmsilva committed May 26, 2024
1 parent 37ad7e2 commit 56292fb
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 12 deletions.
9 changes: 8 additions & 1 deletion src/vs/workbench/api/common/extHostNotebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
throw new files.FileOperationError(localize('err.readonly', "Unable to modify read-only file '{0}'", this._resourceForError(uri)), files.FileOperationResult.FILE_PERMISSION_DENIED);
}


const data: vscode.NotebookData = {
metadata: filter(document.apiNotebook.metadata, key => !(serializer.options?.transientDocumentMetadata ?? {})[key]),
cells: [],
Expand All @@ -360,7 +359,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
// validate write
await this._validateWriteFile(uri, options);

if (token.isCancellationRequested) {
throw new Error('canceled');
}
const bytes = await serializer.serializer.serializeNotebook(data, token);
if (token.isCancellationRequested) {
throw new Error('canceled');
}

// Don't accept any cancellation beyond this point, we need to report the result of the file write
this.trace(`serialized versionId: ${versionId} ${uri.toString()}`);
await this._extHostFileSystem.value.writeFile(uri, bytes);
this.trace(`Finished write versionId: ${versionId} ${uri.toString()}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
*/
export interface IStoredFileWorkingCopyModelFactory<M extends IStoredFileWorkingCopyModel> extends IFileWorkingCopyModelFactory<M> { }

export async function createOptionalResult<T>(callback: (token: CancellationToken) => Promise<T | undefined>, token: CancellationToken): Promise<T | undefined> {
const result = await callback(token);
if (result === undefined && token.isCancellationRequested) {
return undefined;
}
else {
return assertIsDefined(result);
}
}

/**
* The underlying model of a stored file working copy provides some
* methods for the stored file working copy to function. The model is
Expand Down Expand Up @@ -1029,7 +1019,15 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend

// Delegate to working copy model save method if any
if (typeof resolvedFileWorkingCopy.model.save === 'function') {
stat = await resolvedFileWorkingCopy.model.save(writeFileOptions, saveCancellation.token);
try {
stat = await resolvedFileWorkingCopy.model.save(writeFileOptions, saveCancellation.token);
} catch (error) {
if (saveCancellation.token.isCancellationRequested) {
return undefined; // save was cancelled
}

throw error;
}
}

// Otherwise ask for a snapshot and save via file services
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,21 @@ export class TestStoredFileWorkingCopyModelWithCustomSave extends TestStoredFile

saveCounter = 0;
throwOnSave = false;
saveOperation: Promise<void> | undefined = undefined;

async save(options: IWriteFileOptions, token: CancellationToken): Promise<IFileStatWithMetadata> {
if (this.throwOnSave) {
throw new Error('Fail');
}

if (this.saveOperation) {
await this.saveOperation;
}

if (token.isCancellationRequested) {
throw new Error('Canceled');
}

this.saveCounter++;

return {
Expand Down Expand Up @@ -190,6 +199,42 @@ suite('StoredFileWorkingCopy (with custom save)', function () {
assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ERROR), true);
});

test('save cancelled (custom implemented)', async () => {
let savedCounter = 0;
let lastSaveEvent: IStoredFileWorkingCopySaveEvent | undefined = undefined;
disposables.add(workingCopy.onDidSave(e => {
savedCounter++;
lastSaveEvent = e;
}));

let saveErrorCounter = 0;
disposables.add(workingCopy.onDidSaveError(() => {
saveErrorCounter++;
}));

await workingCopy.resolve();
let resolve: () => void;
(workingCopy.model as TestStoredFileWorkingCopyModelWithCustomSave).saveOperation = new Promise(r => resolve = r);

workingCopy.model?.updateContents('first');
const firstSave = workingCopy.save();
// cancel the first save by requesting a second while it is still mid operation
workingCopy.model?.updateContents('second');
const secondSave = workingCopy.save();
resolve!();
await firstSave;
await secondSave;

assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
assert.strictEqual(lastSaveEvent!.reason, SaveReason.EXPLICIT);
assert.ok(lastSaveEvent!.stat);
assert.ok(isStoredFileWorkingCopySaveEvent(lastSaveEvent!));
assert.strictEqual(workingCopy.model?.pushedStackElement, true);
assert.strictEqual((workingCopy.model as TestStoredFileWorkingCopyModelWithCustomSave).saveCounter, 1);
});

ensureNoDisposablesAreLeakedInTestSuite();
});

Expand Down

0 comments on commit 56292fb

Please sign in to comment.