diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index a3a17011b46..89117d73483 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -31,46 +31,47 @@ export class BackupRestorer implements IWorkbenchContribution { this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.doRestoreBackups()); } - private doRestoreBackups(): Promise { + private async doRestoreBackups(): Promise { // Find all files and untitled with backups - return this.backupFileService.getWorkspaceFileBackups().then(backups => { + const backups = await this.backupFileService.getWorkspaceFileBackups(); + const unresolvedBackups = await this.doResolveOpenedBackups(backups); - // Resolve backups that are opened - return this.doResolveOpenedBackups(backups).then((unresolved): Promise | undefined => { + // Some failed to restore or were not opened at all so we open and resolve them manually + if (unresolvedBackups.length > 0) { + await this.doOpenEditors(unresolvedBackups); - // Some failed to restore or were not opened at all so we open and resolve them manually - if (unresolved.length > 0) { - return this.doOpenEditors(unresolved).then(() => this.doResolveOpenedBackups(unresolved)); - } + return this.doResolveOpenedBackups(unresolvedBackups); + } - return undefined; - }); - }); + return undefined; } - private doResolveOpenedBackups(backups: URI[]): Promise { - const restorePromises: Promise[] = []; - const unresolved: URI[] = []; + private async doResolveOpenedBackups(backups: URI[]): Promise { + const unresolvedBackups: URI[] = []; - backups.forEach(backup => { + await Promise.all(backups.map(async backup => { const openedEditor = this.editorService.getOpened({ resource: backup }); if (openedEditor) { - restorePromises.push(openedEditor.resolve().then(undefined, () => unresolved.push(backup))); + try { + await openedEditor.resolve(); // trigger load + } catch (error) { + unresolvedBackups.push(backup); // ignore error and remember as unresolved + } } else { - unresolved.push(backup); + unresolvedBackups.push(backup); } - }); + })); - return Promise.all(restorePromises).then(() => unresolved, () => unresolved); + return unresolvedBackups; } - private doOpenEditors(resources: URI[]): Promise { + private async doOpenEditors(resources: URI[]): Promise { const hasOpenedEditors = this.editorService.visibleEditors.length > 0; const inputs = resources.map((resource, index) => this.resolveInput(resource, index, hasOpenedEditors)); // Open all remaining backups as editors and resolve them to load their backups - return this.editorService.openEditors(inputs).then(() => undefined); + await this.editorService.openEditors(inputs); } private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledResourceInput { diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 09d92f8bf77..21242fc0210 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -47,24 +47,19 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { ); } - private openInternal(input: EditorInput, options: EditorOptions): Promise { + private async openInternal(input: EditorInput, options: EditorOptions): Promise { if (input instanceof FileEditorInput) { input.setForceOpenAsText(); - return this.editorService.openEditor(input, options, this.group).then(() => undefined); + await this.editorService.openEditor(input, options, this.group); } - - return Promise.resolve(); } - private openExternal(resource: URI): void { - this.windowsService.openExternal(resource.toString()).then(didOpen => { - if (!didOpen) { - return this.windowsService.showItemInFolder(resource); - } - - return undefined; - }); + private async openExternal(resource: URI): Promise { + const didOpen = await this.windowsService.openExternal(resource.toString()); + if (!didOpen) { + return this.windowsService.showItemInFolder(resource); + } } getTitle(): string | null { diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 2278db10f68..04fa2d58140 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -132,7 +132,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleDeletes(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { const nonDirtyFileEditors = this.getOpenedFileEditors(false /* non-dirty only */); - nonDirtyFileEditors.forEach(editor => { + nonDirtyFileEditors.forEach(async editor => { const resource = editor.getResource(); // Handle deletes in opened editors depending on: @@ -165,20 +165,17 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // file is really gone and not just a faulty file event. // This only applies to external file events, so we need to check for the isExternal // flag. - let checkExists: Promise; + let exists = false; if (isExternal) { - checkExists = timeout(100).then(() => this.fileService.exists(resource)); - } else { - checkExists = Promise.resolve(false); + await timeout(100); + exists = await this.fileService.exists(resource); } - checkExists.then(exists => { - if (!exists && !editor.isDisposed()) { - editor.dispose(); - } else if (this.environmentService.verbose) { - console.warn(`File exists even though we received a delete event: ${resource.toString()}`); - } - }); + if (!exists && !editor.isDisposed()) { + editor.dispose(); + } else if (this.environmentService.verbose) { + console.warn(`File exists even though we received a delete event: ${resource.toString()}`); + } } }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 174c803f20e..f7a188eb798 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -125,101 +125,103 @@ export class TextFileEditor extends BaseTextEditor { } } - setInput(input: FileEditorInput, options: EditorOptions, token: CancellationToken): Promise { + async setInput(input: FileEditorInput, options: EditorOptions, token: CancellationToken): Promise { // Update/clear view settings if input changes this.doSaveOrClearTextEditorViewState(this.input); // Set input and resolve - return super.setInput(input, options, token).then(() => { - return input.resolve().then(resolvedModel => { + await super.setInput(input, options, token); + try { + const resolvedModel = await input.resolve(); - // Check for cancellation - if (token.isCancellationRequested) { - return undefined; - } + // Check for cancellation + if (token.isCancellationRequested) { + return; + } - // There is a special case where the text editor has to handle binary file editor input: if a binary file - // has been resolved and cached before, it maybe an actual instance of BinaryEditorModel. In this case our text - // editor has to open this model using the binary editor. We return early in this case. - if (resolvedModel instanceof BinaryEditorModel) { - return this.openAsBinary(input, options); - } + // There is a special case where the text editor has to handle binary file editor input: if a binary file + // has been resolved and cached before, it maybe an actual instance of BinaryEditorModel. In this case our text + // editor has to open this model using the binary editor. We return early in this case. + if (resolvedModel instanceof BinaryEditorModel) { + return this.openAsBinary(input, options); + } - const textFileModel = resolvedModel; + const textFileModel = resolvedModel; - // Editor - const textEditor = this.getControl(); - textEditor.setModel(textFileModel.textEditorModel); + // Editor + const textEditor = this.getControl(); + textEditor.setModel(textFileModel.textEditorModel); - // Always restore View State if any associated - const editorViewState = this.loadTextEditorViewState(this.input.getResource()); - if (editorViewState) { - textEditor.restoreViewState(editorViewState); - } + // Always restore View State if any associated + const editorViewState = this.loadTextEditorViewState(this.input.getResource()); + if (editorViewState) { + textEditor.restoreViewState(editorViewState); + } - // TextOptions (avoiding instanceof here for a reason, do not change!) - if (options && types.isFunction((options).apply)) { - (options).apply(textEditor, ScrollType.Immediate); - } + // TextOptions (avoiding instanceof here for a reason, do not change!) + if (options && types.isFunction((options).apply)) { + (options).apply(textEditor, ScrollType.Immediate); + } - // Readonly flag - textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); - }, error => { + // Readonly flag + textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); + } catch (error) { - // In case we tried to open a file inside the text editor and the response - // indicates that this is not a text file, reopen the file through the binary - // editor. - if ((error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) { - return this.openAsBinary(input, options); - } + // In case we tried to open a file inside the text editor and the response + // indicates that this is not a text file, reopen the file through the binary + // editor. + if ((error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) { + return this.openAsBinary(input, options); + } - // Similar, handle case where we were asked to open a folder in the text editor. - if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { - this.openAsFolder(input); + // Similar, handle case where we were asked to open a folder in the text editor. + if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { + this.openAsFolder(input); - return Promise.reject(new Error(nls.localize('openFolderError', "File is a directory"))); - } + throw new Error(nls.localize('openFolderError', "File is a directory")); + } - // Offer to create a file from the error if we have a file not found and the name is valid - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { - return Promise.reject(createErrorWithActions(toErrorMessage(error), { - actions: [ - new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, () => { - return this.textFileService.create(input.getResource()).then(() => this.editorService.openEditor({ - resource: input.getResource(), - options: { - pinned: true // new file gets pinned by default - } - })); - }) - ] - })); - } + // Offer to create a file from the error if we have a file not found and the name is valid + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { + throw createErrorWithActions(toErrorMessage(error), { + actions: [ + new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, async () => { + await this.textFileService.create(input.getResource()); - if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { - const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); + return this.editorService.openEditor({ + resource: input.getResource(), + options: { + pinned: true // new file gets pinned by default + } + }); + }) + ] + }); + } - return Promise.reject(createErrorWithActions(toErrorMessage(error), { - actions: [ - new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { - return this.windowsService.relaunch({ - addArgs: [ - `--max-memory=${memoryLimit}` - ] - }); - }), - new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); - }) - ] - })); - } + if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { + const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - // Otherwise make sure the error bubbles up - return Promise.reject(error); - }); - }); + throw createErrorWithActions(toErrorMessage(error), { + actions: [ + new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { + return this.windowsService.relaunch({ + addArgs: [ + `--max-memory=${memoryLimit}` + ] + }); + }), + new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); + }) + ] + }); + } + + // Otherwise make sure the error bubbles up + throw error; + } } private openAsBinary(input: FileEditorInput, options: EditorOptions): void { @@ -227,21 +229,20 @@ export class TextFileEditor extends BaseTextEditor { this.editorService.openEditor(input, options, this.group); } - private openAsFolder(input: FileEditorInput): void { + private async openAsFolder(input: FileEditorInput): Promise { if (!this.group) { return; } // Since we cannot open a folder, we have to restore the previous input if any and close the editor - this.group.closeEditor(this.input).then(() => { + await this.group.closeEditor(this.input); - // Best we can do is to reveal the folder in the explorer - if (this.contextService.isInsideWorkspace(input.getResource())) { - this.viewletService.openViewlet(VIEWLET_ID).then(() => { - this.explorerService.select(input.getResource(), true); - }); - } - }); + // Best we can do is to reveal the folder in the explorer + if (this.contextService.isInsideWorkspace(input.getResource())) { + await this.viewletService.openViewlet(VIEWLET_ID); + + this.explorerService.select(input.getResource(), true); + } } protected getAriaLabel(): string { diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index a6ca3697b60..996aa2f8031 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -240,26 +240,26 @@ class ResolveSaveConflictAction extends Action { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { const resource = this.model.getResource(); const name = basename(resource); const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); - return TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }).then(() => { - if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { - return; // return if this message is ignored - } + await TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }); - // Show additional help how to resolve the save conflict - const actions: INotificationActions = { primary: [], secondary: [] }; - actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); - actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); + if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { + return; // return if this message is ignored + } - const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); - Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); - pendingResolveSaveConflictMessages.push(handle); - }); + // Show additional help how to resolve the save conflict + const actions: INotificationActions = { primary: [], secondary: [] }; + actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); + actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); + + const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); + Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); + pendingResolveSaveConflictMessages.push(handle); } return Promise.resolve(true); @@ -316,31 +316,28 @@ export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: const editor = control.input; const group = control.group; - resolverService.createModelReference(resource).then(reference => { + resolverService.createModelReference(resource).then(async reference => { const model = reference.object as IResolvedTextFileEditorModel; const localModelSnapshot = model.createSnapshot(); clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions // Revert to be able to save - return model.revert().then(() => { + await model.revert(); - // Restore user value (without loosing undo stack) - modelService.updateModel(model.textEditorModel, createTextBufferFactoryFromSnapshot(localModelSnapshot)); + // Restore user value (without loosing undo stack) + modelService.updateModel(model.textEditorModel, createTextBufferFactoryFromSnapshot(localModelSnapshot)); - // Trigger save - return model.save().then(() => { + // Trigger save + await model.save(); - // Reopen file input - return editorService.openEditor({ resource: model.getResource() }, group).then(() => { + // Reopen file input + await editorService.openEditor({ resource: model.getResource() }, group); - // Clean up - group.closeEditor(editor); - editor.dispose(); - reference.dispose(); - }); - }); - }); + // Clean up + group.closeEditor(editor); + editor.dispose(); + reference.dispose(); }); }; @@ -355,22 +352,20 @@ export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: const editor = control.input; const group = control.group; - resolverService.createModelReference(resource).then(reference => { + resolverService.createModelReference(resource).then(async reference => { const model = reference.object as ITextFileEditorModel; clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions // Revert on model - return model.revert().then(() => { + await model.revert(); - // Reopen file input - return editorService.openEditor({ resource: model.getResource() }, group).then(() => { + // Reopen file input + await editorService.openEditor({ resource: model.getResource() }, group); - // Clean up - group.closeEditor(editor); - editor.dispose(); - reference.dispose(); - }); - }); + // Clean up + group.closeEditor(editor); + editor.dispose(); + reference.dispose(); }); }; diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index ecd630e356a..d146effdb25 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -273,16 +273,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return this.doResolveAsText(); } - private doResolveAsText(): Promise { + private async doResolveAsText(): Promise { // Resolve as text - return this.textFileService.models.loadOrCreate(this.resource, { - mode: this.preferredMode, - encoding: this.preferredEncoding, - reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model - allowBinary: this.forceOpenAsText, - reason: LoadReason.EDITOR - }).then(model => { + try { + await this.textFileService.models.loadOrCreate(this.resource, { + mode: this.preferredMode, + encoding: this.preferredEncoding, + reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model + allowBinary: this.forceOpenAsText, + reason: LoadReason.EDITOR + }); // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary // or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into @@ -292,8 +293,10 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { this.textModelReference = this.textModelResolverService.createModelReference(this.resource); } - return this.textModelReference.then(ref => ref.object as TextFileEditorModel); - }, error => { + const ref = await this.textModelReference; + + return ref.object as TextFileEditorModel; + } catch (error) { // In case of an error that indicates that the file is binary or too large, just return with the binary editor model if ( @@ -304,12 +307,12 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } // Bubble any other error up - return Promise.reject(error); - }); + throw error; + } } - private doResolveAsBinary(): Promise { - return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load().then(m => m as BinaryEditorModel); + private async doResolveAsBinary(): Promise { + return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); } isResolved(): boolean { diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 9b1d56da125..b558791f119 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -143,15 +143,13 @@ export class TextFileContentProvider implements ITextModelContentProvider { @IModelService private readonly modelService: IModelService ) { } - static open(resource: URI, scheme: string, label: string, editorService: IEditorService, options?: ITextEditorOptions): Promise { - return editorService.openEditor( - { - leftResource: TextFileContentProvider.resourceToTextFile(scheme, resource), - rightResource: resource, - label, - options - } - ).then(); + static async open(resource: URI, scheme: string, label: string, editorService: IEditorService, options?: ITextEditorOptions): Promise { + await editorService.openEditor({ + leftResource: TextFileContentProvider.resourceToTextFile(scheme, resource), + rightResource: resource, + label, + options + }); } private static resourceToTextFile(scheme: string, resource: URI): URI { @@ -162,56 +160,55 @@ export class TextFileContentProvider implements ITextModelContentProvider { return resource.with({ scheme: JSON.parse(resource.query)['scheme'], query: null }); } - provideTextContent(resource: URI): Promise { + async provideTextContent(resource: URI): Promise { const savedFileResource = TextFileContentProvider.textFileToResource(resource); // Make sure our text file is resolved up to date - return this.resolveEditorModel(resource).then(codeEditorModel => { + const codeEditorModel = await this.resolveEditorModel(resource); - // Make sure to keep contents up to date when it changes - if (!this.fileWatcherDisposable) { - this.fileWatcherDisposable = this.fileService.onFileChanges(changes => { - if (changes.contains(savedFileResource, FileChangeType.UPDATED)) { - this.resolveEditorModel(resource, false /* do not create if missing */); // update model when resource changes - } - }); - - if (codeEditorModel) { - once(codeEditorModel.onWillDispose)(() => { - dispose(this.fileWatcherDisposable); - this.fileWatcherDisposable = undefined; - }); + // Make sure to keep contents up to date when it changes + if (!this.fileWatcherDisposable) { + this.fileWatcherDisposable = this.fileService.onFileChanges(changes => { + if (changes.contains(savedFileResource, FileChangeType.UPDATED)) { + this.resolveEditorModel(resource, false /* do not create if missing */); // update model when resource changes } - } + }); - return codeEditorModel; - }); + if (codeEditorModel) { + once(codeEditorModel.onWillDispose)(() => { + dispose(this.fileWatcherDisposable); + this.fileWatcherDisposable = undefined; + }); + } + } + + return codeEditorModel; } private resolveEditorModel(resource: URI, createAsNeeded?: true): Promise; private resolveEditorModel(resource: URI, createAsNeeded?: boolean): Promise; - private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise { + private async resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise { const savedFileResource = TextFileContentProvider.textFileToResource(resource); - return this.textFileService.readStream(savedFileResource).then(content => { - let codeEditorModel = this.modelService.getModel(resource); - if (codeEditorModel) { - this.modelService.updateModel(codeEditorModel, content.value); - } else if (createAsNeeded) { - const textFileModel = this.modelService.getModel(savedFileResource); + const content = await this.textFileService.readStream(savedFileResource); - let languageSelector: ILanguageSelection; - if (textFileModel) { - languageSelector = this.modeService.create(textFileModel.getModeId()); - } else { - languageSelector = this.modeService.createByFilepathOrFirstLine(savedFileResource.path); - } + let codeEditorModel = this.modelService.getModel(resource); + if (codeEditorModel) { + this.modelService.updateModel(codeEditorModel, content.value); + } else if (createAsNeeded) { + const textFileModel = this.modelService.getModel(savedFileResource); - codeEditorModel = this.modelService.createModel(content.value, languageSelector, resource); + let languageSelector: ILanguageSelection; + if (textFileModel) { + languageSelector = this.modeService.create(textFileModel.getModeId()); + } else { + languageSelector = this.modeService.createByFilepathOrFirstLine(savedFileResource.path); } - return codeEditorModel; - }); + codeEditorModel = this.modelService.createModel(content.value, languageSelector, resource); + } + + return codeEditorModel; } dispose(): void { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index b607e711aa7..15d60eb041d 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -34,26 +34,25 @@ suite('Files - FileEditorTracker', () => { accessor = instantiationService.createInstance(ServiceAccessor); }); - test('file change event updates model', function () { + test('file change event updates model', async function () { const tracker = instantiationService.createInstance(FileEditorTracker); const resource = toResource.call(this, '/path/index.txt'); - return accessor.textFileService.models.loadOrCreate(resource).then((model: IResolvedTextFileEditorModel) => { - model.textEditorModel.setValue('Super Good'); - assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); + const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel; - return model.save().then(() => { + model.textEditorModel.setValue('Super Good'); + assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); - // change event (watcher) - accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); + await model.save(); - return timeout(0).then(() => { // due to event updating model async - assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); + // change event (watcher) + accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); - tracker.dispose(); - }); - }); - }); + await timeout(0); // due to event updating model async + + assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); + + tracker.dispose(); }); }); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index 97fba151082..bc97910fe3c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -390,7 +390,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { this.rangeHighlightDecorationId = undefined; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { searchValue = searchValue.trim(); // Support to cancel pending outline requests @@ -407,20 +407,19 @@ export class GotoSymbolHandler extends QuickOpenHandler { } // Resolve Outline Model - return this.getOutline().then(outline => { - if (!outline) { - return outline; - } - - if (token.isCancellationRequested) { - return outline; - } - - // Filter by search - outline.applyFilter(searchValue); - + const outline = await this.getOutline(); + if (!outline) { return outline; - }); + } + + if (token.isCancellationRequested) { + return outline; + } + + // Filter by search + outline.applyFilter(searchValue); + + return outline; } getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 933e47b2e73..e347f82a4f7 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -147,43 +147,45 @@ export class OpenFileHandler extends QuickOpenHandler { return this.doFindResults(query, token, this.cacheState.cacheKey, maxSortedResults); } - private doFindResults(query: IPreparedQuery, token: CancellationToken, cacheKey?: string, maxSortedResults?: number): Promise { + private async doFindResults(query: IPreparedQuery, token: CancellationToken, cacheKey?: string, maxSortedResults?: number): Promise { const queryOptions = this.doResolveQueryOptions(query, cacheKey, maxSortedResults); - let iconClass: string; + let iconClass: string | undefined = undefined; if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) { iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise } - return this.getAbsolutePathResult(query).then(result => { - if (token.isCancellationRequested) { - return Promise.resolve({ results: [] }); + let complete: ISearchComplete | undefined = undefined; + + const result = await this.getAbsolutePathResult(query); + if (token.isCancellationRequested) { + complete = { results: [] }; + } + + // If the original search value is an existing file on disk, return it immediately and bypass the search service + else if (result) { + complete = { results: [{ resource: result }] }; + } + + else { + complete = await this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); + } + + const results: QuickOpenEntry[] = []; + + if (!token.isCancellationRequested) { + for (const fileMatch of complete.results) { + const label = basename(fileMatch.resource); + const description = this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true }); + + results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); } + } - // If the original search value is an existing file on disk, return it immediately and bypass the search service - if (result) { - return Promise.resolve({ results: [{ resource: result }] }); - } - - return this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); - }).then(complete => { - const results: QuickOpenEntry[] = []; - - if (!token.isCancellationRequested) { - for (const fileMatch of complete.results) { - - const label = basename(fileMatch.resource); - const description = this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true }); - - results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); - } - } - - return new FileQuickOpenModel(results, complete.stats); - }); + return new FileQuickOpenModel(results, complete.stats); } - private getAbsolutePathResult(query: IPreparedQuery): Promise { + private async getAbsolutePathResult(query: IPreparedQuery): Promise { const detildifiedQuery = untildify(query.original, this.environmentService.userHome); if (isAbsolute(detildifiedQuery)) { const workspaceFolders = this.contextService.getWorkspace().folders; @@ -191,12 +193,16 @@ export class OpenFileHandler extends QuickOpenHandler { workspaceFolders[0].uri.with({ path: detildifiedQuery }) : URI.file(detildifiedQuery); - return this.fileService.resolve(resource).then( - stat => stat.isDirectory ? undefined : resource, - error => undefined); + try { + const stat = await this.fileService.resolve(resource); + + return stat.isDirectory ? undefined : resource; + } catch (error) { + // ignore + } } - return Promise.resolve(undefined); + return undefined; } private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions { diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index ec0319b4d8a..73385b771b4 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -172,7 +172,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } } - private runAction(actionRunner: IActionRunner, actionToRun: IAction, delegate: IContextMenuDelegate, event: IContextMenuEvent): void { + private async runAction(actionRunner: IActionRunner, actionToRun: IAction, delegate: IContextMenuDelegate, event: IContextMenuEvent): Promise { /* __GDPR__ "workbenchActionExecuted" : { "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -182,9 +182,15 @@ class NativeContextMenuService extends Disposable implements IContextMenuService this.telemetryService.publicLog('workbenchActionExecuted', { id: actionToRun.id, from: 'contextMenu' }); const context = delegate.getActionsContext ? delegate.getActionsContext(event) : event; - const res = actionRunner.run(actionToRun, context) || Promise.resolve(null); - res.then(undefined, e => this.notificationService.error(e)); + const runnable = actionRunner.run(actionToRun, context); + if (runnable) { + try { + await runnable; + } catch (error) { + this.notificationService.error(error); + } + } } } diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 3a79b914643..02c36b66e4c 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -78,17 +78,16 @@ class NativeDialogService implements IDialogService { sharedProcessService.registerChannel('dialog', new DialogChannel(this)); } - confirm(confirmation: IConfirmation): Promise { + async confirm(confirmation: IConfirmation): Promise { this.logService.trace('DialogService#confirm', confirmation.message); const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation)); - return this.windowService.showMessageBox(options).then(result => { - return { - confirmed: buttonIndexMap[result.button] === 0 ? true : false, - checkboxChecked: result.checkboxChecked - }; - }); + const result = await this.windowService.showMessageBox(options); + return { + confirmed: buttonIndexMap[result.button] === 0 ? true : false, + checkboxChecked: result.checkboxChecked + }; } private getConfirmOptions(confirmation: IConfirmation): Electron.MessageBoxOptions { @@ -128,7 +127,7 @@ class NativeDialogService implements IDialogService { return opts; } - show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Promise { + async show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); const { options, buttonIndexMap } = this.massageMessageBoxOptions({ @@ -139,7 +138,8 @@ class NativeDialogService implements IDialogService { detail: dialogOptions ? dialogOptions.detail : undefined }); - return this.windowService.showMessageBox(options).then(result => buttonIndexMap[result.button]); + const result = await this.windowService.showMessageBox(options); + return buttonIndexMap[result.button]; } private massageMessageBoxOptions(options: Electron.MessageBoxOptions): IMassagedMessageBoxOptions { diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index fe5bb0a5372..86a5d8bcfb9 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -62,17 +62,16 @@ export class CodeEditorService extends CodeEditorServiceImpl { return this.doOpenCodeEditor(input, source, sideBySide); } - private doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { - return this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(control => { - if (control) { - const widget = control.getControl(); - if (isCodeEditor(widget)) { - return widget; - } + private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + if (control) { + const widget = control.getControl(); + if (isCodeEditor(widget)) { + return widget; } + } - return null; - }); + return null; } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 9b93b9b3975..584d5ce4031 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -328,7 +328,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditors(editors: Array, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise { + async openEditors(editors: Array, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise { // Convert to typed editors and options const typedEditors: IEditorInputWithOptions[] = []; @@ -364,7 +364,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { result.push(group.openEditors(editorsWithOptions)); }); - return Promise.all(result).then(editors => coalesce(editors)); + const openedEditors = await Promise.all(result); + + return coalesce(openedEditors); } //#endregion diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 56b5d19f07e..00e083a9b99 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -71,9 +71,9 @@ export class IntegrityServiceImpl implements IIntegrityService { this.isPure().then(r => { if (r.isPure) { - // all is good - return; + return; // all is good } + this._prompt(); }); } @@ -106,29 +106,25 @@ export class IntegrityServiceImpl implements IIntegrityService { return this._isPurePromise; } - private _isPure(): Promise { + private async _isPure(): Promise { const expectedChecksums = product.checksums || {}; - return this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { - let asyncResults: Promise[] = Object.keys(expectedChecksums).map((filename) => { - return this._resolve(filename, expectedChecksums[filename]); - }); + await this.lifecycleService.when(LifecyclePhase.Eventually); - return Promise.all(asyncResults).then((allResults) => { - let isPure = true; - for (let i = 0, len = allResults.length; i < len; i++) { - if (!allResults[i].isPure) { - isPure = false; - break; - } - } + const allResults = await Promise.all(Object.keys(expectedChecksums).map(filename => this._resolve(filename, expectedChecksums[filename]))); - return { - isPure: isPure, - proof: allResults - }; - }); - }); + let isPure = true; + for (let i = 0, len = allResults.length; i < len; i++) { + if (!allResults[i].isPure) { + isPure = false; + break; + } + } + + return { + isPure: isPure, + proof: allResults + }; } private _resolve(filename: string, expected: string): Promise { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index b3d3a517941..5a37e806e67 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -208,7 +208,8 @@ export class ScopedProgressService extends ScopedService implements IProgressSer }; } - showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { + // Join with existing running promise to ensure progress is accurate if (this.progressState.type === ProgressState.Type.While) { promise = Promise.all([promise, this.progressState.whilePromise]); @@ -217,24 +218,25 @@ export class ScopedProgressService extends ScopedService implements IProgressSer // Keep Promise in State this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); - let stop = () => { + try { + this.doShowWhile(delay); - // If this is not the last promise in the list of joined promises, return early - if (this.progressState.type === ProgressState.Type.While && this.progressState.whilePromise !== promise) { - return; + await promise; + } catch (error) { + // ignore + } finally { + + // If this is not the last promise in the list of joined promises, skip this + if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { + + // The while promise is either null or equal the promise we last hooked on + this.progressState = ProgressState.None; + + if (this.isActive) { + this.progressbar.stop().hide(); + } } - - // The while promise is either null or equal the promise we last hooked on - this.progressState = ProgressState.None; - - if (this.isActive) { - this.progressbar.stop().hide(); - } - }; - - this.doShowWhile(delay); - - return promise.then(stop, stop); + } } private doShowWhile(delay?: number): void { @@ -280,13 +282,15 @@ export class ProgressService implements IProgressService { }; } - showWhile(promise: Promise, delay?: number): Promise { - const stop = () => { + async showWhile(promise: Promise, delay?: number): Promise { + try { + this.progressbar.infinite().show(delay); + + await promise; + } catch (error) { + // ignore + } finally { this.progressbar.stop().hide(); - }; - - this.progressbar.infinite().show(delay); - - return promise.then(stop, stop); + } } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 09a5f340302..ae87e384bb4 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -34,28 +34,28 @@ export class RemoteExtensionEnvironmentChannelClient { constructor(private channel: IChannel) { } - getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI[]): Promise { + async getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI[]): Promise { const args: IGetEnvironmentDataArguments = { language: platform.language, remoteAuthority, extensionDevelopmentPath }; - return this.channel.call('getEnvironmentData', args) - .then((data: IRemoteAgentEnvironmentDTO): IRemoteAgentEnvironment => { - return { - pid: data.pid, - appRoot: URI.revive(data.appRoot), - appSettingsHome: URI.revive(data.appSettingsHome), - settingsPath: URI.revive(data.settingsPath), - logsPath: URI.revive(data.logsPath), - extensionsPath: URI.revive(data.extensionsPath), - extensionHostLogsPath: URI.revive(data.extensionHostLogsPath), - globalStorageHome: URI.revive(data.globalStorageHome), - userHome: URI.revive(data.userHome), - extensions: data.extensions.map(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }), - os: data.os - }; - }); + + const data = await this.channel.call('getEnvironmentData', args); + + return { + pid: data.pid, + appRoot: URI.revive(data.appRoot), + appSettingsHome: URI.revive(data.appSettingsHome), + settingsPath: URI.revive(data.settingsPath), + logsPath: URI.revive(data.logsPath), + extensionsPath: URI.revive(data.extensionsPath), + extensionHostLogsPath: URI.revive(data.extensionHostLogsPath), + globalStorageHome: URI.revive(data.globalStorageHome), + userHome: URI.revive(data.userHome), + extensions: data.extensions.map(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }), + os: data.os + }; } getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 42fc1561a60..ade71f748cd 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -31,7 +31,7 @@ class ResourceModelCollection extends ReferenceCollection { + async createReferencedObject(key: string, skipActivateProvider?: boolean): Promise { this.modelsToDispose.delete(key); const resource = URI.parse(key); @@ -43,15 +43,19 @@ class ResourceModelCollection extends ReferenceCollection this.instantiationService.createInstance(ResourceEditorModel, resource)); + await this.resolveTextModelContent(key); + + return this.instantiationService.createInstance(ResourceEditorModel, resource); } // Either unknown schema, or not yet registered, try to activate if (!skipActivateProvider) { - return this.fileService.activateProvider(resource.scheme).then(() => this.createReferencedObject(key, true)); + await this.fileService.activateProvider(resource.scheme); + + return this.createReferencedObject(key, true); } - return Promise.reject(new Error('resource is not available')); + throw new Error('resource is not available'); } destroyReferencedObject(key: string, modelPromise: Promise): void { @@ -101,18 +105,17 @@ class ResourceModelCollection extends ReferenceCollection { + private async resolveTextModelContent(key: string): Promise { const resource = URI.parse(key); const providers = this.providers[resource.scheme] || []; const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); - return first(factories).then(model => { - if (!model) { - return Promise.reject(new Error('resource is not available')); - } + const model = await first(factories); + if (!model) { + throw new Error('resource is not available'); + } - return model; - }); + return model; } } @@ -131,14 +134,16 @@ export class TextModelResolverService implements ITextModelService { } createModelReference(resource: URI): Promise> { - return this._createModelReference(resource); + return this.doCreateModelReference(resource); } - private _createModelReference(resource: URI): Promise> { + private async doCreateModelReference(resource: URI): Promise> { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - return this.untitledEditorService.loadOrCreate({ resource }).then(model => new ImmortalReference(model as IResolvedTextEditorModel)); + const model = await this.untitledEditorService.loadOrCreate({ resource }); + + return new ImmortalReference(model as IResolvedTextEditorModel); } // InMemory Schema: go through model service cache @@ -154,14 +159,15 @@ export class TextModelResolverService implements ITextModelService { const ref = this.resourceModelCollection.acquire(resource.toString()); - return ref.object.then( - model => ({ object: model, dispose: () => ref.dispose() }), - err => { - ref.dispose(); + try { + const model = await ref.object; - return Promise.reject(err); - } - ); + return { object: model as IResolvedTextEditorModel, dispose: () => ref.dispose() }; + } catch (error) { + ref.dispose(); + + throw error; + } } registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index b09b8d1d520..b1c60ab4252 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -71,6 +71,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { return undefined; // only interested when window is closing or loading } + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { return undefined; // only care about untitled workspaces to ask for saving @@ -190,16 +191,23 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } } - private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise { - return this.contextService.updateFolders(foldersToAdd, foldersToDelete, index) - .then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error)); + private async doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise { + try { + await this.contextService.updateFolders(foldersToAdd, foldersToDelete, index); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } } addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): Promise { return this.doAddFolders(foldersToAdd, undefined, donotNotifyError); } - private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { + private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { const state = this.contextService.getWorkbenchState(); // If we are in no-workspace or single-folder workspace, adding folders has to @@ -217,11 +225,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } // Delegate addition of folders to workspace service otherwise - return this.contextService.addFolders(foldersToAdd, index) - .then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error)); + try { + await this.contextService.addFolders(foldersToAdd, index); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } } - removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise { + async removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise { // If we are in single-folder state and the opened folder is to be removed, // we create an empty workspace and enter it. @@ -230,8 +245,15 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } // Delegate removal of folders to workspace service otherwise - return this.contextService.removeFolders(foldersToRemove) - .then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error)); + try { + await this.contextService.removeFolders(foldersToRemove); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } } private includesSingleFolderWorkspace(folders: URI[]): boolean { @@ -283,10 +305,12 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."), noLink: true }; - return this.windowService.showMessageBox(options).then(() => false); + await this.windowService.showMessageBox(options); + + return false; } - return Promise.resolve(true); // OK + return true; // OK } private async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise {