提交 c983b2ae 编写于 作者: I isidor

explorer actions: use bulk edit service

fixes #9390
上级 21c04900
......@@ -13,7 +13,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Action } from 'vs/base/common/actions';
import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { VIEWLET_ID, IExplorerService, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ByteSize, IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet';
......@@ -53,6 +52,7 @@ import { IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/p
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IBulkEditService, ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");
......@@ -123,7 +123,7 @@ export class NewFolderAction extends Action {
}
}
async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
async function deleteFiles(explorerService: IExplorerService, bulkEditService: IBulkEditService, 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");
......@@ -226,7 +226,12 @@ async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dial
// Call function
try {
await workingCopyFileService.delete(distinctElements.map(e => e.resource), { useTrash, recursive: true });
const resourceFileEdits = distinctElements.map(e => new ResourceFileEdit(e.resource, undefined, { recursive: true }));
// TODO@Isidor respect the useTrash parameter
await bulkEditService.apply(resourceFileEdits, {
undoRedoSource: explorerService.undoRedoSource,
label: distinctElements.length > 1 ? nls.localize('deleteBulkEdit', "Delete {0} files", distinctElements.length) : nls.localize('deleteFileBulkEdit', "Delete {0}", distinctElements[0].name)
});
} catch (error) {
// Handle error to delete file(s) from a modal confirmation dialog
......@@ -256,7 +261,7 @@ async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dial
skipConfirm = true;
return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
return deleteFiles(explorerService, bulkEditService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
}
}
}
......@@ -874,12 +879,11 @@ function onErrorWithRetry(notificationService: INotificationService, error: unkn
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
const textFileService = accessor.get(ITextFileService);
const editorService = accessor.get(IEditorService);
const viewsService = accessor.get(IViewsService);
const notificationService = accessor.get(INotificationService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const commandService = accessor.get(ICommandService);
const bulkEditService = accessor.get(IBulkEditService);
const view = await viewsService.openView(VIEW_ID, true);
if (!view) {
......@@ -910,12 +914,18 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
const onSuccess = async (value: string): Promise<void> => {
try {
const created = isFolder ? await workingCopyFileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value));
const resourceToCreate = resources.joinPath(folder.resource, value);
await bulkEditService.apply([new ResourceFileEdit(undefined, resourceToCreate, { folder: isFolder })], {
undoRedoSource: explorerService.undoRedoSource,
label: nls.localize('newBulkEdit', "New {0}", value)
});
await refreshIfSeparator(value, explorerService);
isFolder ?
await explorerService.select(created.resource, true) :
await editorService.openEditor({ resource: created.resource, options: { pinned: true } });
if (isFolder) {
await explorerService.select(resourceToCreate, true);
} else {
await editorService.openEditor({ resource: resourceToCreate, options: { pinned: true } });
}
} catch (error) {
onErrorWithRetry(notificationService, error, () => onSuccess(value));
}
......@@ -949,7 +959,7 @@ CommandsRegistry.registerCommand({
export const renameHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const bulkEditService = accessor.get(IBulkEditService);
const notificationService = accessor.get(INotificationService);
const stats = explorerService.getContext(false);
......@@ -966,7 +976,10 @@ export const renameHandler = async (accessor: ServicesAccessor) => {
const targetResource = resources.joinPath(parentResource, value);
if (stat.resource.toString() !== targetResource.toString()) {
try {
await workingCopyFileService.move([{ source: stat.resource, target: targetResource }]);
await bulkEditService.apply([new ResourceFileEdit(stat.resource, targetResource)], {
undoRedoSource: explorerService.undoRedoSource,
label: nls.localize('renameBulkEdit', "Rename {0} to {1}", stat.name, value)
});
await refreshIfSeparator(value, explorerService);
} catch (e) {
notificationService.error(e);
......@@ -982,7 +995,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(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
await deleteFiles(accessor.get(IExplorerService), accessor.get(IBulkEditService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
}
};
......@@ -991,7 +1004,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
await deleteFiles(accessor.get(IExplorerService), accessor.get(IBulkEditService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
}
};
......@@ -1018,8 +1031,8 @@ export const DOWNLOAD_COMMAND_ID = 'explorer.download';
const downloadFileHandler = (accessor: ServicesAccessor) => {
const logService = accessor.get(ILogService);
const fileService = accessor.get(IFileService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const fileDialogService = accessor.get(IFileDialogService);
const bulkEditService = accessor.get(IBulkEditService);
const explorerService = accessor.get(IExplorerService);
const progressService = accessor.get(IProgressService);
......@@ -1238,7 +1251,10 @@ const downloadFileHandler = (accessor: ServicesAccessor) => {
});
if (destination) {
await workingCopyFileService.copy([{ source: explorerItem.resource, target: destination }], { overwrite: true });
await bulkEditService.apply([new ResourceFileEdit(explorerItem.resource, destination, { overwrite: true, copy: true })], {
undoRedoSource: explorerService.undoRedoSource,
label: nls.localize('downloadBulkEdit', "Download {0}", explorerItem.name)
});
} else {
cts.cancel(); // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100
}
......@@ -1259,8 +1275,8 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
const clipboardService = accessor.get(IClipboardService);
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const notificationService = accessor.get(INotificationService);
const bulkEditService = accessor.get(IBulkEditService);
const editorService = accessor.get(IEditorService);
const configurationService = accessor.get(IConfigurationService);
const uriIdentityService = accessor.get(IUriIdentityService);
......@@ -1293,20 +1309,28 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
}));
// Move/Copy File
let stats: IFileStatWithMetadata[] = [];
if (pasteShouldMove) {
stats = await workingCopyFileService.move(sourceTargetPairs);
const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target));
await bulkEditService.apply(resourceFileEdits, {
undoRedoSource: explorerService.undoRedoSource,
label: sourceTargetPairs.length > 1 ? nls.localize('moveBulkEdit', "Move {0} files", sourceTargetPairs.length) : nls.localize('moveFileBulkEdit', "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target))
});
} else {
stats = await workingCopyFileService.copy(sourceTargetPairs);
const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true }));
await bulkEditService.apply(resourceFileEdits, {
undoRedoSource: explorerService.undoRedoSource,
label: sourceTargetPairs.length > 1 ? nls.localize('copyBulkEdit', "Copy {0} files", sourceTargetPairs.length) : nls.localize('copyFileBulkEdit', "Copy {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target))
});
}
if (stats.length >= 1) {
const stat = stats[0];
if (stat && !stat.isDirectory && stats.length === 1) {
await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } });
}
if (stat) {
await explorerService.select(stat.resource);
if (sourceTargetPairs.length >= 1) {
const pair = sourceTargetPairs[0];
await explorerService.select(pair.target);
if (sourceTargetPairs.length === 1) {
const item = explorerService.findClosest(pair.target);
if (item && !item.isDirectory) {
await editorService.openEditor({ resource: item.resource, options: { pinned: true, preserveFocus: true } });
}
}
}
} catch (e) {
......
......@@ -19,7 +19,7 @@ import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editor
import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
......@@ -40,6 +40,8 @@ import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWat
import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig';
import { DirtyFilesIndicator } from 'vs/workbench/contrib/files/common/dirtyFilesIndicator';
import { isEqual } from 'vs/base/common/resources';
import { UndoCommand, RedoCommand } from 'vs/editor/browser/editorExtensions';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
// Viewlet Action
export class OpenExplorerViewletAction extends ShowViewletAction {
......@@ -485,3 +487,25 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
},
order: 1
});
UndoCommand.addImplementation(110, (accessor: ServicesAccessor) => {
const undoRedoService = accessor.get(IUndoRedoService);
const explorerService = accessor.get(IExplorerService);
if (explorerService.hasViewFocus()) {
undoRedoService.undo(explorerService.undoRedoSource);
return true;
}
return false;
});
RedoCommand.addImplementation(110, (accessor: ServicesAccessor) => {
const undoRedoService = accessor.get(IUndoRedoService);
const explorerService = accessor.get(IExplorerService);
if (explorerService.hasViewFocus()) {
undoRedoService.redo(explorerService.undoRedoSource);
return true;
}
return false;
});
......@@ -20,7 +20,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, IExplorerService, VIEW_ID } from 'vs/workbench/contrib/files/common/files';
import { dirname, joinPath, basename, distinctParents } from 'vs/base/common/resources';
import { dirname, joinPath, basename, distinctParents, basenameOrAuthority } from 'vs/base/common/resources';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { localize } from 'vs/nls';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
......@@ -37,7 +37,6 @@ import { Schemas } from 'vs/base/common/network';
import { NativeDragAndDropData, 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 { 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';
......@@ -58,6 +57,7 @@ import { IEditableData } from 'vs/workbench/common/views';
import { IEditorInput } from 'vs/workbench/common/editor';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IBulkEditService, ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
......@@ -809,11 +809,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService,
@IHostService private hostService: IHostService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
@IProgressService private readonly progressService: IProgressService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IBulkEditService private readonly bulkEditService: IBulkEditService
) {
this.toDispose = [];
......@@ -1074,7 +1074,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
continue;
}
await this.workingCopyFileService.delete([joinPath(target.resource, entry.name)], { recursive: true });
await this.bulkEditService.apply([new ResourceFileEdit(joinPath(target.resource, entry.name), undefined, { recursive: true })], {
undoRedoSource: this.explorerService.undoRedoSource,
label: localize('overwrite', "Overwrite {0}", entry.name)
});
if (token.isCancellationRequested) {
break;
......@@ -1348,10 +1351,15 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
progress.report({ message: sourceFileName });
const stat = (await this.workingCopyFileService.copy([{ source: sourceFile, target: targetFile }], { overwrite: true }))[0];
await this.bulkEditService.apply([new ResourceFileEdit(sourceFile, targetFile, { overwrite: true, copy: true })], {
undoRedoSource: this.explorerService.undoRedoSource,
label: localize('copyFile', "Copy {0}", sourceFileName)
});
// 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 } });
const item = this.explorerService.findClosest(targetFile);
if (resources.length === 1 && item && !item.isDirectory) {
this.editorService.openEditor({ resource: item.resource, options: { pinned: true } });
}
});
}));
......@@ -1440,9 +1448,16 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
private async doHandleExplorerDropOnCopy(sources: ExplorerItem[], target: ExplorerItem): Promise<void> {
// Reuse duplicate action when user copies
const incrementalNaming = this.configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
const sourceTargetPairs = sources.map(({ resource, isDirectory }) => ({ source: resource, target: findValidPasteFileTarget(this.explorerService, target, { resource, isDirectory, allowOverwrite: false }, incrementalNaming) }));
const stats = await this.workingCopyFileService.copy(sourceTargetPairs);
const editors = stats.filter(stat => !stat.isDirectory).map(({ resource }) => ({ resource, options: { pinned: true } }));
const resourceFileEdits = sources.map(({ resource, isDirectory }) => (new ResourceFileEdit(resource, findValidPasteFileTarget(this.explorerService, target, { resource, isDirectory, allowOverwrite: false }, incrementalNaming), { copy: true })));
await this.bulkEditService.apply(resourceFileEdits, {
undoRedoSource: this.explorerService.undoRedoSource,
label: resourceFileEdits.length > 1 ? localize('copy', "Copy {0} files", resourceFileEdits.length) : localize('copyOneFile', "Copy {0}", basenameOrAuthority(resourceFileEdits[0].newResource!))
});
const editors = resourceFileEdits.filter(edit => {
const item = edit.newResource ? this.explorerService.findClosest(edit.newResource) : undefined;
return item && !item.isDirectory;
}).map(edit => ({ resource: edit.newResource, options: { pinned: true } }));
await this.editorService.openEditors(editors);
}
......@@ -1450,18 +1465,19 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
private async doHandleExplorerDropOnMove(sources: ExplorerItem[], target: ExplorerItem): Promise<void> {
// Do not allow moving readonly items
const sourceTargetPairs = sources.filter(source => !source.isReadonly).map(source => ({ source: source.resource, target: joinPath(target.resource, source.name) }));
const resourceFileEdits = sources.filter(source => !source.isReadonly).map(source => new ResourceFileEdit(source.resource, joinPath(target.resource, source.name)));
const label = sources.length > 1 ? localize('move', "Move {0} files", sources.length) : localize('moveOneFile', "Move {0}", sources[0].name);
try {
await this.workingCopyFileService.move(sourceTargetPairs);
await this.bulkEditService.apply(resourceFileEdits, { undoRedoSource: this.explorerService.undoRedoSource, label });
} catch (error) {
// Conflict
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
const overwrites: URI[] = [];
for (const { target } of sourceTargetPairs) {
if (await this.fileService.exists(target)) {
overwrites.push(target);
for (const edit of resourceFileEdits) {
if (edit.newResource && await this.fileService.exists(edit.newResource)) {
overwrites.push(edit.newResource);
}
}
......@@ -1470,7 +1486,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
const { confirmed } = await this.dialogService.confirm(confirm);
if (confirmed) {
try {
await this.workingCopyFileService.move(sourceTargetPairs, { overwrite: true });
await this.bulkEditService.apply(resourceFileEdits.map(re => new ResourceFileEdit(re.oldResource, re.newResource, { overwrite: true })), {
undoRedoSource: this.explorerService.undoRedoSource,
label
});
} catch (error) {
this.notificationService.error(error);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册