diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 6be968e338f4a3cd65428700eb5dea48eb54bc26..4bf84682dba5ba3c846d6002f0195cfe8931405e 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -133,8 +133,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } private saveAllDirty(options?: ISaveOptions): void { - for (const workingCopy of this.workingCopyService.workingCopies) { - if (workingCopy.isDirty() && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { + for (const workingCopy of this.workingCopyService.dirtyWorkingCopies) { + if (!(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { workingCopy.save(options); } } diff --git a/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts index 8c17c7525524954acb9e394b18e6cd2d6055fd49..607607f678c755ae40a0de769f8dcb8765bf15a1 100644 --- a/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts +++ b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts @@ -34,7 +34,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi // copies that have not been backed up yet and then prevent the // shutdown if that is the case. - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (!dirtyWorkingCopies.length) { return false; // no dirty: no veto } diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts index 8e1053caa6519b64ba8a553c84facb07dc30ef89..e6e17435bc321aa74ef09c180bc7f7d0228100b3 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts @@ -46,7 +46,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi private onBeforeShutdown(reason: ShutdownReason): boolean | Promise { // Dirty working copies need treatment on shutdown - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (dirtyWorkingCopies.length) { // If auto save is enabled, save all working copies and then check again for dirty copies @@ -55,7 +55,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi return this.doSaveAll(dirtyWorkingCopies, false /* not untitled */, { skipSaveParticipants: true }).then(() => { // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved - const remainingDirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (remainingDirtyWorkingCopies.length) { return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); } @@ -143,7 +143,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi private async confirmBeforeShutdown(): Promise { // Show confirm dialog for all dirty working copies - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; const confirm = await this.fileDialogService.showSaveConfirm(dirtyWorkingCopies.map(w => w.resource)); // Save diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3774785acbcf94f6f9a6d0204699c11895015dbd..c6ccc289d7d8b4870181c726740870b53e953e81 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -143,7 +143,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(workingCopyService: IWorkingCopyService, textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { 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"); @@ -155,16 +155,16 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi // Handle dirty let confirmed = true; - const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource))); - if (dirty.length) { + const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource))); + if (dirtyWorkingCopies.length) { let message: string; if (distinctElements.length > 1) { message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); } else if (distinctElements[0].isDirectory) { - if (dirty.length === 1) { + if (dirtyWorkingCopies.length === 1) { message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?"); } else { - message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length); + message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirtyWorkingCopies.length); } } else { message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?"); @@ -181,7 +181,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi confirmed = false; } else { skipConfirm = true; - await textFileService.revertAll(dirty); + await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert())); } } @@ -190,11 +190,11 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi return; } - let confirmDeletePromise: Promise; + let confirmation: IConfirmationResult; // Check if we need to ask for confirmation at all if (skipConfirm || (useTrash && configurationService.getValue(CONFIRM_DELETE_SETTING_KEY) === false)) { - confirmDeletePromise = Promise.resolve({ confirmed: true }); + confirmation = { confirmed: true }; } // Confirm for moving to trash @@ -207,7 +207,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi detail += distinctElements.length > 1 ? nls.localize('undoTrashFiles', "You can restore these files from the Trash.") : nls.localize('undoTrash', "You can restore this file from the Trash."); } - confirmDeletePromise = dialogService.confirm({ + confirmation = await dialogService.confirm({ message, detail, primaryButton, @@ -223,7 +223,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi let { message, detail } = getDeleteMessage(distinctElements); detail += detail ? '\n' : ''; detail += nls.localize('irreversible', "This action is irreversible!"); - confirmDeletePromise = dialogService.confirm({ + confirmation = await dialogService.confirm({ message, detail, primaryButton, @@ -231,61 +231,54 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi }); } - return confirmDeletePromise.then(confirmation => { - // Check for confirmation checkbox - let updateConfirmSettingsPromise: Promise = Promise.resolve(undefined); - if (confirmation.confirmed && confirmation.checkboxChecked === true) { - updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); - } - return updateConfirmSettingsPromise.then(() => { + // Check for confirmation checkbox + if (confirmation.confirmed && confirmation.checkboxChecked === true) { + await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); + } - // Check for confirmation - if (!confirmation.confirmed) { - return Promise.resolve(undefined); - } - // Call function - const servicePromise = Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))) - .then(undefined, (error: any) => { - // Handle error to delete file(s) from a modal confirmation dialog - let errorMessage: string; - let detailMessage: string | undefined; - let primaryButton: string; - if (useTrash) { - errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); - detailMessage = nls.localize('irreversible', "This action is irreversible!"); - primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); - } else { - errorMessage = toErrorMessage(error, false); - primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); - } + // Check for confirmation + if (!confirmation.confirmed) { + return; + } - return dialogService.confirm({ - message: errorMessage, - detail: detailMessage, - type: 'warning', - primaryButton - }).then(res => { + // Call function + try { + await Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))); + } catch (error) { - if (res.confirmed) { - if (useTrash) { - useTrash = false; // Delete Permanently - } + // Handle error to delete file(s) from a modal confirmation dialog + let errorMessage: string; + let detailMessage: string | undefined; + let primaryButton: string; + if (useTrash) { + errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); + detailMessage = nls.localize('irreversible', "This action is irreversible!"); + primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); + } else { + errorMessage = toErrorMessage(error, false); + primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); + } - skipConfirm = true; + const res = await dialogService.confirm({ + message: errorMessage, + detail: detailMessage, + type: 'warning', + primaryButton + }); - return deleteFiles(textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); - } + if (res.confirmed) { + if (useTrash) { + useTrash = false; // Delete Permanently + } - return Promise.resolve(); - }); - }); + skipConfirm = true; - return servicePromise.then(undefined); - }); - }); + return deleteFiles(workingCopyService, textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); + } + } } function getMoveToTrashMessage(distinctElements: ExplorerItem[]): { message: string, detail: string } { @@ -648,7 +641,7 @@ export class ShowActiveFileInExplorer extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); @@ -656,7 +649,7 @@ export class ShowActiveFileInExplorer extends Action { this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } - return Promise.resolve(true); + return true; } } @@ -726,7 +719,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -738,7 +731,7 @@ export class ShowOpenedFileInNewWindow extends Action { this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } - return Promise.resolve(true); + return true; } } @@ -822,7 +815,7 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { @@ -839,7 +832,7 @@ export class CompareWithClipboardAction extends Action { }); } - return Promise.resolve(true); + return true; } dispose(): void { @@ -902,15 +895,17 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole folder.addChild(newStat); - const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); - return createPromise.then(created => { + const onSuccess = async (value: string): Promise => { + try { + const created = isFolder ? await fileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value)); refreshIfSeparator(value, explorerService); - return isFolder ? explorerService.select(created.resource, true) - : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); - }, error => { + + isFolder ? + await explorerService.select(created.resource, true) : + await editorService.openEditor({ resource: created.resource, options: { pinned: true } }); + } catch (error) { onErrorWithRetry(notificationService, error, () => onSuccess(value)); - }); + } }; explorerService.setEditable(newStat, { @@ -974,7 +969,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(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); + await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } }; @@ -983,7 +978,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => { const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); } }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index bb781656124576259e3819553ff163b4591e5596..cf6c906c91db97f58ed3581f97a648fda2175c21 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -200,7 +200,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: COMPARE_SELECTED_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const explorerService = accessor.get(IExplorerService); const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService); @@ -212,7 +212,7 @@ CommandsRegistry.registerCommand({ }); } - return Promise.resolve(true); + return true; } }); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index c3543e4c824041ec56e24bc0ec786845da2881f8..3193a3dd81cbc85262f3a78dfbcd314159424130 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -81,6 +81,8 @@ export interface IWorkingCopyService { readonly dirtyCount: number; + readonly dirtyWorkingCopies: IWorkingCopy[]; + readonly hasDirty: boolean; isDirty(resource: URI): boolean; @@ -118,46 +120,6 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#endregion - //#region Dirty Tracking - - isDirty(resource: URI): boolean { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - if (workingCopies) { - for (const workingCopy of workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } - } - - return false; - } - - get hasDirty(): boolean { - for (const workingCopy of this._workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } - - return false; - } - - get dirtyCount(): number { - let totalDirtyCount = 0; - - for (const workingCopy of this._workingCopies) { - if (workingCopy.isDirty()) { - totalDirtyCount++; - } - } - - return totalDirtyCount; - } - - //#endregion - - //#region Registry private mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); @@ -216,6 +178,50 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } //#endregion + + + //#region Dirty Tracking + + get hasDirty(): boolean { + for (const workingCopy of this._workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + + return false; + } + + get dirtyCount(): number { + let totalDirtyCount = 0; + + for (const workingCopy of this._workingCopies) { + if (workingCopy.isDirty()) { + totalDirtyCount++; + } + } + + return totalDirtyCount; + } + + get dirtyWorkingCopies(): IWorkingCopy[] { + return this.workingCopies.filter(workingCopy => workingCopy.isDirty()); + } + + isDirty(resource: URI): boolean { + const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); + if (workingCopies) { + for (const workingCopy of workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + } + + return false; + } + + //#endregion } registerSingleton(IWorkingCopyService, WorkingCopyService, true); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index b94eb61e384067c1ed6472a5f81ec4d05aa12d5e..a6a5269f462217171ce007b41b28e466f2c1eb00 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -105,7 +105,10 @@ suite('WorkingCopyService', () => { copy1.setDirty(true); + assert.equal(copy1.isDirty(), true); assert.equal(service.dirtyCount, 1); + assert.equal(service.dirtyWorkingCopies.length, 1); + assert.equal(service.dirtyWorkingCopies[0], copy1); assert.equal(service.isDirty(resource1), true); assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1);