mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
file working copy - better cancellation support
This commit is contained in:
parent
5e3df4ce30
commit
7f9835ac43
|
@ -942,7 +942,7 @@ export class TaskSequentializer {
|
|||
}
|
||||
|
||||
setPending(taskId: number, promise: Promise<void>, onCancel?: () => void,): Promise<void> {
|
||||
this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise };
|
||||
this._pending = { taskId, cancel: () => onCancel?.(), promise };
|
||||
|
||||
promise.then(() => this.donePending(taskId), () => this.donePending(taskId));
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
|
|||
// Backup does not exist
|
||||
else {
|
||||
const backup = await workingCopy.backup(token);
|
||||
await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta);
|
||||
await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta, token);
|
||||
|
||||
backups.push(workingCopy);
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class RenameOperation implements IFileOperation {
|
|||
return new Noop();
|
||||
}
|
||||
|
||||
await this._workingCopyFileService.move(moves, this._undoRedoInfo, token);
|
||||
await this._workingCopyFileService.move(moves, token, this._undoRedoInfo);
|
||||
return new RenameOperation(undoes, { isUndoing: true }, this._workingCopyFileService, this._fileService);
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ class CopyOperation implements IFileOperation {
|
|||
}
|
||||
|
||||
// (2) perform the actual copy and use the return stats to build undo edits
|
||||
const stats = await this._workingCopyFileService.copy(copies, this._undoRedoInfo, token);
|
||||
const stats = await this._workingCopyFileService.copy(copies, token, this._undoRedoInfo);
|
||||
const undoes: DeleteEdit[] = [];
|
||||
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
|
@ -190,8 +190,8 @@ class CreateOperation implements IFileOperation {
|
|||
return new Noop();
|
||||
}
|
||||
|
||||
await this._workingCopyFileService.createFolder(folderCreates, this._undoRedoInfo, token);
|
||||
await this._workingCopyFileService.create(fileCreates, this._undoRedoInfo, token);
|
||||
await this._workingCopyFileService.createFolder(folderCreates, token, this._undoRedoInfo);
|
||||
await this._workingCopyFileService.create(fileCreates, token, this._undoRedoInfo);
|
||||
|
||||
return this._instaService.createInstance(DeleteOperation, undoes, { isUndoing: true });
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ class DeleteOperation implements IFileOperation {
|
|||
return new Noop();
|
||||
}
|
||||
|
||||
await this._workingCopyFileService.delete(deletes, this._undoRedoInfo, token);
|
||||
await this._workingCopyFileService.delete(deletes, token, this._undoRedoInfo);
|
||||
|
||||
if (undoes.length === 0) {
|
||||
return new Noop();
|
||||
|
|
|
@ -149,7 +149,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
return [bufferStream, decoder];
|
||||
}
|
||||
|
||||
async create(operations: { resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions }[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
async create(operations: { resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions }[], undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
const operationsWithContents: ICreateFileOperation[] = await Promise.all(operations.map(async o => {
|
||||
const contents = await this.getEncodedReadable(o.resource, o.value);
|
||||
return {
|
||||
|
@ -159,7 +159,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
};
|
||||
}));
|
||||
|
||||
return this.workingCopyFileService.create(operationsWithContents, undoInfo, token);
|
||||
return this.workingCopyFileService.create(operationsWithContents, CancellationToken.None, undoInfo);
|
||||
}
|
||||
|
||||
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
|
||||
|
@ -251,7 +251,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
// However, this will only work if the source exists
|
||||
// and is not orphaned, so we need to check that too.
|
||||
if (this.fileService.canHandleResource(source) && this.uriIdentityService.extUri.isEqual(source, target) && (await this.fileService.exists(source))) {
|
||||
await this.workingCopyFileService.move([{ file: { source, target } }]);
|
||||
await this.workingCopyFileService.move([{ file: { source, target } }], CancellationToken.None);
|
||||
|
||||
// At this point we don't know whether we have a
|
||||
// model for the source or the target URI so we
|
||||
|
|
|
@ -95,7 +95,7 @@ export interface ITextFileService extends IDisposable {
|
|||
* Create files. If the file exists it will be overwritten with the contents if
|
||||
* the options enable to overwrite.
|
||||
*/
|
||||
create(operations: { resource: URI, value?: string | ITextSnapshot, options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
|
||||
create(operations: { resource: URI, value?: string | ITextSnapshot, options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]>;
|
||||
|
||||
/**
|
||||
* Returns the readable that uses the appropriate encoding.
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { ETAG_DISABLED, FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, IFileStatWithMetadata, IFileStreamContent } from 'vs/platform/files/common/files';
|
||||
import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { TaskSequentializer, timeout } from 'vs/base/common/async';
|
||||
import { raceCancellation, TaskSequentializer, timeout } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { DefaultEndOfLine, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
@ -914,7 +914,9 @@ export class FileWorkingCopy<T extends IFileWorkingCopyModel> extends Disposable
|
|||
// cancel the pending operation so that ours can run
|
||||
// before the pending one finishes.
|
||||
// Currently this will try to cancel pending save
|
||||
// participants but never a pending save.
|
||||
// participants and pending snapshots from the
|
||||
// save operation, but not the actual save which does
|
||||
// not support cancellation yet.
|
||||
this.saveSequentializer.cancelPending();
|
||||
|
||||
// Register this as the next upcoming save and return
|
||||
|
@ -970,15 +972,9 @@ export class FileWorkingCopy<T extends IFileWorkingCopyModel> extends Disposable
|
|||
}
|
||||
|
||||
// It is possible that a subsequent save is cancelling this
|
||||
// running save. As such we return early when we detect that
|
||||
// However, we do not pass the token into the file service
|
||||
// because that is an atomic operation currently without
|
||||
// cancellation support, so we dispose the cancellation if
|
||||
// it was not cancelled yet.
|
||||
// running save. As such we return early when we detect that.
|
||||
if (saveCancellation.token.isCancellationRequested) {
|
||||
return;
|
||||
} else {
|
||||
saveCancellation.dispose();
|
||||
}
|
||||
|
||||
// We have to protect against being disposed at this point. It could be that the save() operation
|
||||
|
@ -1012,10 +1008,22 @@ export class FileWorkingCopy<T extends IFileWorkingCopyModel> extends Disposable
|
|||
try {
|
||||
|
||||
// Snapshot working copy model contents
|
||||
const snapshot = await resolvedFileWorkingCopy.model.snapshot(CancellationToken.None);
|
||||
const snapshot = await raceCancellation(resolvedFileWorkingCopy.model.snapshot(saveCancellation.token), saveCancellation.token);
|
||||
|
||||
// It is possible that a subsequent save is cancelling this
|
||||
// running save. As such we return early when we detect that
|
||||
// However, we do not pass the token into the file service
|
||||
// because that is an atomic operation currently without
|
||||
// cancellation support, so we dispose the cancellation if
|
||||
// it was not cancelled yet.
|
||||
if (saveCancellation.token.isCancellationRequested) {
|
||||
return;
|
||||
} else {
|
||||
saveCancellation.dispose();
|
||||
}
|
||||
|
||||
// Write them to disk
|
||||
const stat = await this.fileService.writeFile(lastResolvedFileStat.resource, snapshot, {
|
||||
const stat = await this.fileService.writeFile(lastResolvedFileStat.resource, assertIsDefined(snapshot), {
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource)) ? ETAG_DISABLED : lastResolvedFileStat.etag,
|
||||
unlock: options.writeUnlock
|
||||
|
@ -1025,7 +1033,7 @@ export class FileWorkingCopy<T extends IFileWorkingCopyModel> extends Disposable
|
|||
} catch (error) {
|
||||
this.handleSaveError(error, versionId, options);
|
||||
}
|
||||
})());
|
||||
})(), () => saveCancellation.cancel());
|
||||
})(), () => saveCancellation.cancel());
|
||||
}
|
||||
|
||||
|
@ -1248,8 +1256,12 @@ export class FileWorkingCopy<T extends IFileWorkingCopyModel> extends Disposable
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const snapshot = await this.model.snapshot(token);
|
||||
const contents = await streamToBuffer(snapshot);
|
||||
const snapshot = await raceCancellation(this.model.snapshot(token), token);
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const contents = await streamToBuffer(assertIsDefined(snapshot));
|
||||
|
||||
return stringToSnapshot(contents.toString());
|
||||
}
|
||||
|
|
|
@ -626,7 +626,7 @@ export class FileWorkingCopyManager<T extends IFileWorkingCopyModel> extends Dis
|
|||
|
||||
// Create target file adhoc if it does not exist yet
|
||||
if (!targetExists) {
|
||||
await this.workingCopyFileService.create([{ resource: target }]);
|
||||
await this.workingCopyFileService.create([{ resource: target }], CancellationToken.None);
|
||||
}
|
||||
|
||||
// At this point we need to resolve the target working copy
|
||||
|
|
|
@ -27,7 +27,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable {
|
|||
return toDisposable(() => remove());
|
||||
}
|
||||
|
||||
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise<void> {
|
||||
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken): Promise<void> {
|
||||
const timeout = this.configurationService.getValue<number>('files.participants.timeout');
|
||||
if (timeout <= 0) {
|
||||
return; // disabled
|
||||
|
@ -36,7 +36,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable {
|
|||
// For each participant
|
||||
for (const participant of this.participants) {
|
||||
try {
|
||||
await participant.participate(files, operation, undoInfo, timeout, token ?? CancellationToken.None);
|
||||
await participant.participate(files, operation, undoInfo, timeout, token);
|
||||
} catch (err) {
|
||||
this.logService.warn(err);
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ export interface IWorkingCopyFileService {
|
|||
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
|
||||
* `onDidRunWorkingCopyFileOperation` events to participate.
|
||||
*/
|
||||
create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
|
||||
create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]>;
|
||||
|
||||
/**
|
||||
* Will create a folder and any parent folder that needs to be created.
|
||||
|
@ -176,7 +176,7 @@ export interface IWorkingCopyFileService {
|
|||
* Note: events will only be emitted for the provided resource, but not any
|
||||
* parent folders that are being created as part of the operation.
|
||||
*/
|
||||
createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
|
||||
createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]>;
|
||||
|
||||
/**
|
||||
* Will move working copies matching the provided resources and corresponding children
|
||||
|
@ -185,7 +185,7 @@ export interface IWorkingCopyFileService {
|
|||
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
|
||||
* `onDidRunWorkingCopyFileOperation` events to participate.
|
||||
*/
|
||||
move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
|
||||
move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]>;
|
||||
|
||||
/**
|
||||
* Will copy working copies matching the provided resources and corresponding children
|
||||
|
@ -194,7 +194,7 @@ export interface IWorkingCopyFileService {
|
|||
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
|
||||
* `onDidRunWorkingCopyFileOperation` events to participate.
|
||||
*/
|
||||
copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
|
||||
copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]>;
|
||||
|
||||
/**
|
||||
* Will delete working copies matching the provided resources and children
|
||||
|
@ -203,7 +203,7 @@ export interface IWorkingCopyFileService {
|
|||
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
|
||||
* `onDidRunWorkingCopyFileOperation` events to participate.
|
||||
*/
|
||||
delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void>;
|
||||
delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<void>;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
@ -272,15 +272,15 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
|
||||
//#region File operations
|
||||
|
||||
create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doCreateFileOrFolder(operations, true, undoInfo, token);
|
||||
create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doCreateFileOrFolder(operations, true, token, undoInfo);
|
||||
}
|
||||
|
||||
createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doCreateFileOrFolder(operations, false, undoInfo, token);
|
||||
createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doCreateFileOrFolder(operations, false, token, undoInfo);
|
||||
}
|
||||
|
||||
async doCreateFileOrFolder(operations: (ICreateFileOperation | ICreateOperation)[], isFile: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
async doCreateFileOrFolder(operations: (ICreateFileOperation | ICreateOperation)[], isFile: boolean, token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
if (operations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
|
||||
// before events
|
||||
const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files };
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
// now actually create on disk
|
||||
let stats: IFileStatWithMetadata[];
|
||||
|
@ -313,26 +313,26 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
} catch (error) {
|
||||
|
||||
// error event
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// after event
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
async move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doMoveOrCopy(operations, true, undoInfo, token);
|
||||
async move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doMoveOrCopy(operations, true, token, undoInfo);
|
||||
}
|
||||
|
||||
async copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doMoveOrCopy(operations, false, undoInfo, token);
|
||||
async copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
return this.doMoveOrCopy(operations, false, token, undoInfo);
|
||||
}
|
||||
|
||||
private async doMoveOrCopy(operations: IMoveOperation[] | ICopyOperation[], move: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
|
||||
private async doMoveOrCopy(operations: IMoveOperation[] | ICopyOperation[], move: boolean, token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> {
|
||||
const stats: IFileStatWithMetadata[] = [];
|
||||
|
||||
// validate move/copy operation before starting
|
||||
|
@ -349,7 +349,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
|
||||
// before event
|
||||
const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, files };
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
try {
|
||||
for (const { file: { source, target }, overwrite } of operations) {
|
||||
|
@ -372,18 +372,18 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
} catch (error) {
|
||||
|
||||
// error event
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// after event
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void> {
|
||||
async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<void> {
|
||||
|
||||
// validate delete operation before starting
|
||||
for (const operation of operations) {
|
||||
|
@ -399,7 +399,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
|
||||
// before events
|
||||
const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, files };
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
// check for any existing dirty working copies for the resource
|
||||
// and do a soft revert before deleting to be able to close
|
||||
|
@ -417,13 +417,13 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
} catch (error) {
|
||||
|
||||
// error event
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// after event
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
@ -437,7 +437,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
return this.fileOperationParticipants.addFileOperationParticipant(participant);
|
||||
}
|
||||
|
||||
private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise<void> {
|
||||
private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken): Promise<void> {
|
||||
return this.fileOperationParticipants.participate(files, operation, undoInfo, token);
|
||||
}
|
||||
|
||||
|
|
|
@ -404,26 +404,27 @@ suite('FileWorkingCopy', function () {
|
|||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
|
||||
// multiple saves in parallel are fine and result
|
||||
// in individual save operations when content changes
|
||||
// in just one save operation (the second one
|
||||
// cancels the first)
|
||||
workingCopy.model?.updateContents('hello save');
|
||||
const firstSave = workingCopy.save();
|
||||
workingCopy.model?.updateContents('hello save more');
|
||||
const secondSave = workingCopy.save();
|
||||
|
||||
await Promises.settled([firstSave, secondSave]);
|
||||
assert.strictEqual(savedCounter, 5);
|
||||
assert.strictEqual(savedCounter, 4);
|
||||
assert.strictEqual(saveErrorCounter, 0);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
|
||||
// no save when not forced and not dirty
|
||||
await workingCopy.save();
|
||||
assert.strictEqual(savedCounter, 5);
|
||||
assert.strictEqual(savedCounter, 4);
|
||||
assert.strictEqual(saveErrorCounter, 0);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
|
||||
// save when forced even when not dirty
|
||||
await workingCopy.save({ force: true });
|
||||
assert.strictEqual(savedCounter, 6);
|
||||
assert.strictEqual(savedCounter, 5);
|
||||
assert.strictEqual(saveErrorCounter, 0);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
|
||||
|
@ -437,7 +438,7 @@ suite('FileWorkingCopy', function () {
|
|||
assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), true);
|
||||
|
||||
await workingCopy.save({ force: true });
|
||||
assert.strictEqual(savedCounter, 7);
|
||||
assert.strictEqual(savedCounter, 6);
|
||||
assert.strictEqual(saveErrorCounter, 0);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), false);
|
||||
|
|
|
@ -13,6 +13,7 @@ import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
|
|||
import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { TestFileWorkingCopyModel, TestFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
suite('FileWorkingCopyManager', () => {
|
||||
|
||||
|
@ -248,7 +249,7 @@ suite('FileWorkingCopyManager', () => {
|
|||
workingCopy.model?.updateContents('hello create');
|
||||
assert.strictEqual(workingCopy.isDirty(), true);
|
||||
|
||||
await accessor.workingCopyFileService.create([{ resource }]);
|
||||
await accessor.workingCopyFileService.create([{ resource }], CancellationToken.None);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
});
|
||||
|
||||
|
@ -269,9 +270,9 @@ suite('FileWorkingCopyManager', () => {
|
|||
assert.strictEqual(sourceWorkingCopy.isDirty(), true);
|
||||
|
||||
if (move) {
|
||||
await accessor.workingCopyFileService.move([{ file: { source, target } }]);
|
||||
await accessor.workingCopyFileService.move([{ file: { source, target } }], CancellationToken.None);
|
||||
} else {
|
||||
await accessor.workingCopyFileService.copy([{ file: { source, target } }]);
|
||||
await accessor.workingCopyFileService.copy([{ file: { source, target } }], CancellationToken.None);
|
||||
}
|
||||
|
||||
const targetWorkingCopy = await manager.resolve(target);
|
||||
|
@ -286,7 +287,7 @@ suite('FileWorkingCopyManager', () => {
|
|||
workingCopy.model?.updateContents('hello delete');
|
||||
assert.strictEqual(workingCopy.isDirty(), true);
|
||||
|
||||
await accessor.workingCopyFileService.delete([{ resource }]);
|
||||
await accessor.workingCopyFileService.delete([{ resource }], CancellationToken.None);
|
||||
assert.strictEqual(workingCopy.isDirty(), false);
|
||||
});
|
||||
|
||||
|
@ -297,7 +298,7 @@ suite('FileWorkingCopyManager', () => {
|
|||
sourceWorkingCopy.model?.updateContents('hello move');
|
||||
assert.strictEqual(sourceWorkingCopy.isDirty(), true);
|
||||
|
||||
await accessor.workingCopyFileService.move([{ file: { source, target: source } }]);
|
||||
await accessor.workingCopyFileService.move([{ file: { source, target: source } }], CancellationToken.None);
|
||||
|
||||
assert.strictEqual(sourceWorkingCopy.isDirty(), true);
|
||||
assert.strictEqual(sourceWorkingCopy.model?.contents, 'hello move');
|
||||
|
|
|
@ -14,6 +14,8 @@ import { FileOperation } from 'vs/platform/files/common/files';
|
|||
import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ICopyOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
suite('WorkingCopyFileService', () => {
|
||||
|
||||
|
@ -224,7 +226,7 @@ suite('WorkingCopyFileService', () => {
|
|||
eventCounter++;
|
||||
});
|
||||
|
||||
await accessor.workingCopyFileService.createFolder([{ resource }]);
|
||||
await accessor.workingCopyFileService.createFolder([{ resource }], CancellationToken.None);
|
||||
|
||||
assert.strictEqual(eventCounter, 3);
|
||||
|
||||
|
@ -233,6 +235,60 @@ suite('WorkingCopyFileService', () => {
|
|||
listener2.dispose();
|
||||
});
|
||||
|
||||
test('cancellation of participants', async function () {
|
||||
const resource = toResource.call(this, '/path/folder');
|
||||
|
||||
let canceled = false;
|
||||
const participant = accessor.workingCopyFileService.addFileOperationParticipant({
|
||||
participate: async (files, operation, info, t, token) => {
|
||||
await timeout(0);
|
||||
canceled = token.isCancellationRequested;
|
||||
}
|
||||
});
|
||||
|
||||
// Create
|
||||
let cts = new CancellationTokenSource();
|
||||
let promise: Promise<unknown> = accessor.workingCopyFileService.create([{ resource }], cts.token);
|
||||
cts.cancel();
|
||||
await promise;
|
||||
assert.strictEqual(canceled, true);
|
||||
canceled = false;
|
||||
|
||||
// Create Folder
|
||||
cts = new CancellationTokenSource();
|
||||
promise = accessor.workingCopyFileService.createFolder([{ resource }], cts.token);
|
||||
cts.cancel();
|
||||
await promise;
|
||||
assert.strictEqual(canceled, true);
|
||||
canceled = false;
|
||||
|
||||
// Move
|
||||
cts = new CancellationTokenSource();
|
||||
promise = accessor.workingCopyFileService.move([{ file: { source: resource, target: resource } }], cts.token);
|
||||
cts.cancel();
|
||||
await promise;
|
||||
assert.strictEqual(canceled, true);
|
||||
canceled = false;
|
||||
|
||||
// Copy
|
||||
cts = new CancellationTokenSource();
|
||||
promise = accessor.workingCopyFileService.copy([{ file: { source: resource, target: resource } }], cts.token);
|
||||
cts.cancel();
|
||||
await promise;
|
||||
assert.strictEqual(canceled, true);
|
||||
canceled = false;
|
||||
|
||||
// Delete
|
||||
cts = new CancellationTokenSource();
|
||||
promise = accessor.workingCopyFileService.delete([{ resource }], cts.token);
|
||||
cts.cancel();
|
||||
await promise;
|
||||
assert.strictEqual(canceled, true);
|
||||
canceled = false;
|
||||
|
||||
participant.dispose();
|
||||
});
|
||||
|
||||
async function testEventsMoveOrCopy(files: ICopyOperation[], move?: boolean): Promise<number> {
|
||||
let eventCounter = 0;
|
||||
|
||||
|
@ -251,9 +307,9 @@ suite('WorkingCopyFileService', () => {
|
|||
});
|
||||
|
||||
if (move) {
|
||||
await accessor.workingCopyFileService.move(files);
|
||||
await accessor.workingCopyFileService.move(files, CancellationToken.None);
|
||||
} else {
|
||||
await accessor.workingCopyFileService.copy(files);
|
||||
await accessor.workingCopyFileService.copy(files, CancellationToken.None);
|
||||
}
|
||||
|
||||
participant.dispose();
|
||||
|
@ -331,9 +387,9 @@ suite('WorkingCopyFileService', () => {
|
|||
});
|
||||
|
||||
if (move) {
|
||||
await accessor.workingCopyFileService.move(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })));
|
||||
await accessor.workingCopyFileService.move(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })), CancellationToken.None);
|
||||
} else {
|
||||
await accessor.workingCopyFileService.copy(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })));
|
||||
await accessor.workingCopyFileService.copy(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })), CancellationToken.None);
|
||||
}
|
||||
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
|
@ -407,7 +463,7 @@ suite('WorkingCopyFileService', () => {
|
|||
eventCounter++;
|
||||
});
|
||||
|
||||
await accessor.workingCopyFileService.delete(models.map(m => ({ resource: m.resource })));
|
||||
await accessor.workingCopyFileService.delete(models.map(m => ({ resource: m.resource })), CancellationToken.None);
|
||||
for (const model of models) {
|
||||
assert.ok(!accessor.workingCopyService.isDirty(model.resource));
|
||||
model.dispose();
|
||||
|
@ -459,7 +515,7 @@ suite('WorkingCopyFileService', () => {
|
|||
eventCounter++;
|
||||
});
|
||||
|
||||
await accessor.workingCopyFileService.create([{ resource, contents }]);
|
||||
await accessor.workingCopyFileService.create([{ resource, contents }], CancellationToken.None);
|
||||
assert.ok(!accessor.workingCopyService.isDirty(model.resource));
|
||||
model.dispose();
|
||||
|
||||
|
|
|
@ -186,18 +186,18 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService {
|
|||
|
||||
addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { return Disposable.None; }
|
||||
|
||||
async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void> { }
|
||||
async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<void> { }
|
||||
|
||||
registerWorkingCopyProvider(provider: (resourceOrFolder: URI) => IWorkingCopy[]): IDisposable { return Disposable.None; }
|
||||
|
||||
getDirty(resource: URI): IWorkingCopy[] { return []; }
|
||||
|
||||
create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
|
||||
move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
|
||||
copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
|
||||
}
|
||||
|
||||
export function mock<T>(): Ctor<T> {
|
||||
|
|
Loading…
Reference in a new issue