From ba5c31503ad0acdef405463463db71626bd00258 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2018 15:54:54 +0100 Subject: [PATCH] multi select - add and use distinctParents method to avoid duplicates --- src/vs/base/common/resources.ts | 26 +++++++++-- src/vs/base/test/common/resources.test.ts | 43 +++++++++++++++++++ .../files/electron-browser/fileActions.ts | 42 +++++++++--------- 3 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 src/vs/base/test/common/resources.test.ts diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 4b883338383..3012d4a5b15 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -12,9 +12,9 @@ export function basenameOrAuthority(resource: uri): string { return paths.basename(resource.fsPath) || resource.authority; } -export function isEqualOrParent(first: uri, second: uri, ignoreCase?: boolean): boolean { - if (first.scheme === second.scheme && first.authority === second.authority) { - return paths.isEqualOrParent(first.fsPath, second.fsPath, ignoreCase); +export function isEqualOrParent(resource: uri, candidate: uri, ignoreCase?: boolean): boolean { + if (resource.scheme === candidate.scheme && resource.authority === candidate.authority) { + return paths.isEqualOrParent(resource.fsPath, candidate.fsPath, ignoreCase); } return false; @@ -42,3 +42,23 @@ export function dirname(resource: uri): uri { path: paths.dirname(resource.path) }); } + +export function distinctParents(items: T[], resourceAccessor: (item: T) => uri): T[] { + const distinctParents: T[] = []; + for (let i = 0; i < items.length; i++) { + const candidateResource = resourceAccessor(items[i]); + if (items.some((otherItem, index) => { + if (index === i) { + return false; + } + + return isEqualOrParent(candidateResource, resourceAccessor(otherItem)); + })) { + continue; + } + + distinctParents.push(items[i]); + } + + return distinctParents; +} \ No newline at end of file diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts new file mode 100644 index 00000000000..f9275777a53 --- /dev/null +++ b/src/vs/base/test/common/resources.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import URI from 'vs/base/common/uri'; +import { distinctParents } from 'vs/base/common/resources'; + +suite('Resources', () => { + + test('distinctParents', () => { + + // Basic + let resources = [ + URI.file('/some/folderA/file.txt'), + URI.file('/some/folderB/file.txt'), + URI.file('/some/folderC/file.txt') + ]; + + let distinct = distinctParents(resources, r => r); + assert.equal(distinct.length, 3); + assert.equal(distinct[0].toString(), resources[0].toString()); + assert.equal(distinct[1].toString(), resources[1].toString()); + assert.equal(distinct[2].toString(), resources[2].toString()); + + // Parent / Child + resources = [ + URI.file('/some/folderA'), + URI.file('/some/folderA/file.txt'), + URI.file('/some/folderA/child/file.txt'), + URI.file('/some/folderA2/file.txt'), + URI.file('/some/file.txt') + ]; + + distinct = distinctParents(resources, r => r); + assert.equal(distinct.length, 3); + assert.equal(distinct[0].toString(), resources[0].toString()); + assert.equal(distinct[1].toString(), resources[3].toString()); + assert.equal(distinct[2].toString(), resources[4].toString()); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 5fcd05b354e..79e7b568601 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -51,6 +51,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IListService, ListWidget } from 'vs/platform/list/browser/listService'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { distinctParents } from 'vs/base/common/resources'; export interface IEditableData { action: IAction; @@ -659,14 +660,16 @@ class BaseDeleteFileAction extends BaseFileAction { primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete"); } + const distinctElements = distinctParents(this.elements, e => e.resource); + // Handle dirty let confirmDirtyPromise: TPromise = TPromise.as(true); - const dirty = this.textFileService.getDirty().filter(d => this.elements.some(e => resources.isEqualOrParent(d, e.resource, !isLinux /* ignorecase */))); + const dirty = this.textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource, !isLinux /* ignorecase */))); if (dirty.length) { let message: string; - if (this.elements.length > 1) { + if (distinctElements.length > 1) { message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); - } else if (this.elements[0].isDirectory) { + } else if (distinctElements[0].isDirectory) { if (dirty.length === 1) { message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?"); } else { @@ -706,9 +709,9 @@ class BaseDeleteFileAction extends BaseFileAction { // Confirm for moving to trash else if (this.useTrash) { - const message = this.elements.length > 1 ? getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", this.elements.length), this.elements.map(e => e.resource)) - : this.elements[0].isDirectory ? nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", this.elements[0].name) - : nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", this.elements[0].name); + const message = distinctElements.length > 1 ? getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource)) + : distinctElements[0].isDirectory ? nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name) + : nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name); confirmDeletePromise = this.messageService.confirmWithCheckbox({ message, detail: isWindows ? nls.localize('undoBin', "You can restore from the recycle bin.") : nls.localize('undoTrash', "You can restore from the trash."), @@ -722,9 +725,9 @@ class BaseDeleteFileAction extends BaseFileAction { // Confirm for deleting permanently else { - const message = this.elements.length > 1 ? getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", this.elements.length), this.elements.map(e => e.resource)) - : this.elements[0].isDirectory ? nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", this.elements[0].name) - : nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", this.elements[0].name); + const message = distinctElements.length > 1 ? getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource)) + : distinctElements[0].isDirectory ? nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name) + : nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name); confirmDeletePromise = this.messageService.confirmWithCheckbox({ message, detail: nls.localize('irreversible', "This action is irreversible!"), @@ -749,21 +752,20 @@ class BaseDeleteFileAction extends BaseFileAction { } // Call function - const servicePromise = TPromise.join(this.elements.map(e => this.fileService.del(e.resource, this.useTrash))).then(() => { - if (this.elements[0].parent) { - this.tree.setFocus(this.elements[0].parent); // move focus to parent + const servicePromise = TPromise.join(distinctElements.map(e => this.fileService.del(e.resource, this.useTrash))).then(() => { + if (distinctElements[0].parent) { + this.tree.setFocus(distinctElements[0].parent); // move focus to parent } }, (error: any) => { - if (this.elements.length === 1) { - // Allow to retry - let extraAction: Action; - if (this.useTrash) { - extraAction = new Action('permanentDelete', nls.localize('permDelete', "Delete Permanently"), null, true, () => { this.useTrash = false; this.skipConfirm = true; return this.run(); }); - } - - this.onErrorWithRetry(error, () => this.run(), extraAction); + + // Allow to retry + let extraAction: Action; + if (this.useTrash) { + extraAction = new Action('permanentDelete', nls.localize('permDelete', "Delete Permanently"), null, true, () => { this.useTrash = false; this.skipConfirm = true; return this.run(); }); } + this.onErrorWithRetry(error, () => this.run(), extraAction); + // Focus back to tree this.tree.DOMFocus(); }); -- GitLab