Skip to content

Commit

Permalink
working copy - expose file IO operations for participation (#84672)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Feb 18, 2020
1 parent fe6e7b5 commit b6e4587
Show file tree
Hide file tree
Showing 14 changed files with 611 additions and 281 deletions.
22 changes: 14 additions & 8 deletions src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { URI } from 'vs/base/common/uri';
import { IWaitUntil } from 'vs/base/common/event';

@extHostCustomer
export class MainThreadFileSystemEventService {
Expand All @@ -28,6 +31,7 @@ export class MainThreadFileSystemEventService {
@IProgressService progressService: IProgressService,
@IConfigurationService configService: IConfigurationService,
@ILogService logService: ILogService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
) {

const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
Expand Down Expand Up @@ -66,40 +70,42 @@ export class MainThreadFileSystemEventService {
messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants..."));
messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants..."));


this._listener.add(textFileService.onWillRunOperation(e => {

function participateInFileOperation(e: IWaitUntil, operation: FileOperation, target: URI, source?: URI): void {
const timeout = configService.getValue<number>('files.participants.timeout');
if (timeout <= 0) {
return; // disabled
}

const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => {

progress.report({ message: messages.get(e.operation) });
progress.report({ message: messages.get(operation) });

return new Promise((resolve, reject) => {

const cts = new CancellationTokenSource();

const timeoutHandle = setTimeout(() => {
logService.trace('CANCELLED file participants because of timeout', timeout, e.target, e.operation);
logService.trace('CANCELLED file participants because of timeout', timeout, target, operation);
cts.cancel();
reject(new Error('timeout'));
}, timeout);

proxy.$onWillRunFileOperation(e.operation, e.target, e.source, timeout, cts.token)
proxy.$onWillRunFileOperation(operation, target, source, timeout, cts.token)
.then(resolve, reject)
.finally(() => clearTimeout(timeoutHandle));
});

});

e.waitUntil(p);
}));
}

this._listener.add(textFileService.onWillCreateTextFile(e => participateInFileOperation(e, FileOperation.CREATE, e.resource)));
this._listener.add(workingCopyFileService.onBeforeWorkingCopyFileOperation(e => participateInFileOperation(e, e.operation, e.target, e.source)));

// AFTER file operation
this._listener.add(textFileService.onDidRunOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source)));
this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined)));
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source)));
}

dispose(): void {
Expand Down
25 changes: 13 additions & 12 deletions src/vs/workbench/contrib/files/browser/fileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { sequence } from 'vs/base/common/async';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';

export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");
Expand Down Expand Up @@ -144,7 +145,7 @@ export class GlobalNewUntitledFileAction extends Action {
}
}

async function deleteFiles(workingCopyService: IWorkingCopyService, textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
let primaryButton: string;
if (useTrash) {
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
Expand Down Expand Up @@ -244,7 +245,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ

// Call function
try {
await Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true })));
await Promise.all(distinctElements.map(e => workingCopyFileService.delete(e.resource, { useTrash: useTrash, recursive: true })));
} catch (error) {

// Handle error to delete file(s) from a modal confirmation dialog
Expand Down Expand Up @@ -274,7 +275,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ

skipConfirm = true;

return deleteFiles(workingCopyService, textFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
return deleteFiles(workingCopyService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
}
}
}
Expand Down Expand Up @@ -934,7 +935,7 @@ CommandsRegistry.registerCommand({

export const renameHandler = (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
const textFileService = accessor.get(ITextFileService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const notificationService = accessor.get(INotificationService);

const stats = explorerService.getContext(false);
Expand All @@ -951,7 +952,7 @@ export const renameHandler = (accessor: ServicesAccessor) => {
const targetResource = resources.joinPath(parentResource, value);
if (stat.resource.toString() !== targetResource.toString()) {
try {
await textFileService.move(stat.resource, targetResource);
await workingCopyFileService.move(stat.resource, targetResource);
refreshIfSeparator(value, explorerService);
} catch (e) {
notificationService.error(e);
Expand All @@ -967,7 +968,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
}
};

Expand All @@ -976,7 +977,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
const stats = explorerService.getContext(true).filter(s => !s.isRoot);

if (stats.length) {
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
}
};

Expand All @@ -1002,7 +1003,7 @@ export const cutFileHandler = (accessor: ServicesAccessor) => {
export const DOWNLOAD_COMMAND_ID = 'explorer.download';
const downloadFileHandler = (accessor: ServicesAccessor) => {
const fileService = accessor.get(IFileService);
const textFileService = accessor.get(ITextFileService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const fileDialogService = accessor.get(IFileDialogService);
const explorerService = accessor.get(IExplorerService);
const stats = explorerService.getContext(true);
Expand Down Expand Up @@ -1037,7 +1038,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => {
defaultUri
});
if (destination) {
await textFileService.copy(s.resource, destination, true);
await workingCopyFileService.copy(s.resource, destination, true);
} else {
// User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100
canceled = true;
Expand All @@ -1055,7 +1056,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
const clipboardService = accessor.get(IClipboardService);
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
const textFileService = accessor.get(ITextFileService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const notificationService = accessor.get(INotificationService);
const editorService = accessor.get(IEditorService);
const configurationService = accessor.get(IConfigurationService);
Expand Down Expand Up @@ -1087,9 +1088,9 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {

// Move/Copy File
if (pasteShouldMove) {
return await textFileService.move(fileToPaste, targetFile);
return await workingCopyFileService.move(fileToPaste, targetFile);
} else {
return await textFileService.copy(fileToPaste, targetFile);
return await workingCopyFileService.copy(fileToPaste, targetFile);
}
} catch (e) {
onError(notificationService, new Error(nls.localize('fileDeleted', "The file to paste has been deleted or moved since you copied it. {0}", getErrorMessage(e))));
Expand Down
12 changes: 6 additions & 6 deletions src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { Schemas } from 'vs/base/common/network';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { IDialogService, IConfirmation, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { URI } from 'vs/base/common/uri';
Expand Down Expand Up @@ -641,7 +641,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService,
@IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService,
@IHostService private hostService: IHostService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
@IWorkingCopyService private workingCopyService: IWorkingCopyService
Expand Down Expand Up @@ -953,7 +953,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}

const copyTarget = joinPath(target.resource, basename(sourceFile));
const stat = await this.textFileService.copy(sourceFile, copyTarget, true);
const stat = await this.workingCopyFileService.copy(sourceFile, copyTarget, true);
// if we only add one file, just open it directly
if (resources.length === 1 && !stat.isDirectory) {
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
Expand Down Expand Up @@ -1040,7 +1040,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Reuse duplicate action if user copies
if (isCopy) {
const incrementalNaming = this.configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming));
const stat = await this.workingCopyFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming));
if (!stat.isDirectory) {
await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
}
Expand All @@ -1056,7 +1056,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}

try {
await this.textFileService.move(source.resource, targetResource);
await this.workingCopyFileService.move(source.resource, targetResource);
} catch (error) {
// Conflict
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
Expand All @@ -1065,7 +1065,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
const { confirmed } = await this.dialogService.confirm(confirm);
if (confirmed) {
try {
await this.textFileService.move(source.resource, targetResource, true /* overwrite */);
await this.workingCopyFileService.move(source.resource, targetResource, true /* overwrite */);
} catch (error) {
this.notificationService.error(error);
}
Expand Down
6 changes: 4 additions & 2 deletions src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';


type ValidationResult = { canApply: true } | { canApply: false, reason: URI };
Expand Down Expand Up @@ -237,6 +238,7 @@ class BulkEdit {
@ILogService private readonly _logService: ILogService,
@IFileService private readonly _fileService: IFileService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
this._editor = editor;
Expand Down Expand Up @@ -309,7 +311,7 @@ class BulkEdit {
if (options.overwrite === undefined && options.ignoreIfExists && await this._fileService.exists(edit.newUri)) {
continue; // not overwriting, but ignoring, and the target file exists
}
await this._textFileService.move(edit.oldUri, edit.newUri, options.overwrite);
await this._workingCopyFileService.move(edit.oldUri, edit.newUri, options.overwrite);

} else if (!edit.newUri && edit.oldUri) {
// delete file
Expand All @@ -318,7 +320,7 @@ class BulkEdit {
if (useTrash && !(this._fileService.hasCapability(edit.oldUri, FileSystemProviderCapabilities.Trash))) {
useTrash = false; // not supported by provider
}
await this._textFileService.delete(edit.oldUri, { useTrash, recursive: options.recursive });
await this._workingCopyFileService.delete(edit.oldUri, { useTrash, recursive: options.recursive });
} else if (!options.ignoreIfNotExists) {
throw new Error(`${edit.oldUri} does not exist and can not be deleted`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/
import { ILabelService } from 'vs/platform/label/common/label';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';

class TestEnvironmentService extends NativeWorkbenchEnvironmentService {

Expand Down Expand Up @@ -108,6 +109,8 @@ suite('KeybindingsEditing', () => {
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
instantiationService.stub(IBackupFileService, new TestBackupFileService());
Expand Down
Loading

0 comments on commit b6e4587

Please sign in to comment.