diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 8f4d5491d6e7..4ed0642a0382 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -279,7 +279,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { }); // Toolbar actions - const removeGroupAction = this._register(new Action(CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), 'close-editor-group', true, () => { this.accessor.removeGroup(this); return Promise.resolve(true); })); + const removeGroupAction = this._register(new Action( + CLOSE_EDITOR_GROUP_COMMAND_ID, + localize('closeGroupAction', "Close"), + 'close-editor-group', + true, + () => { + this.accessor.removeGroup(this); + + return Promise.resolve(true); + })); + const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id); containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined }); } @@ -402,7 +412,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise { if (this._group.count === 0) { - return Promise.resolve(); // nothing to show + return; // nothing to show } // Determine editor options @@ -415,7 +425,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const activeEditor = this._group.activeEditor; if (!activeEditor) { - return Promise.resolve(); + return; } options.pinned = this._group.isPinned(activeEditor); // preserve pinned state @@ -1102,7 +1112,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async handleDirty(editors: EditorInput[]): Promise { if (!editors.length) { - return Promise.resolve(false); // no veto + return false; // no veto } const editor = editors.shift()!; @@ -1135,7 +1145,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened ) { - return Promise.resolve(false); + return false; } // Switch to editor that we want to handle and confirm to save/revert diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 8adaeea18506..4d7e0baf088c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format } from 'vs/base/common/strings'; import { extname, basename } from 'vs/base/common/resources'; -import { areFunctions, withNullAsUndefined } from 'vs/base/common/types'; +import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; @@ -893,7 +893,7 @@ export class ChangeModeAction extends Action { // Compute mode let currentModeId: string | undefined; - let modeId: string; + let modeId: string | undefined; if (textModel) { modeId = textModel.getLanguageIdentifier().language; currentModeId = this.modeService.getLanguageName(modeId) || undefined; @@ -933,9 +933,9 @@ export class ChangeModeAction extends Action { } // Offer action to configure via settings - let configureModeAssociations: IQuickPickItem; - let configureModeSettings: IQuickPickItem; - let galleryAction: Action; + let configureModeAssociations: IQuickPickItem | undefined; + let configureModeSettings: IQuickPickItem | undefined; + let galleryAction: Action | undefined; if (hasLanguageSupport && resource) { const ext = extname(resource) || basename(resource); @@ -959,56 +959,55 @@ export class ChangeModeAction extends Action { picks.unshift(autoDetectMode); } - return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => { - if (!pick) { - return; + const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); + if (!pick) { + return; + } + + if (pick === galleryAction) { + galleryAction.run(); + return; + } + + // User decided to permanently configure associations, return right after + if (pick === configureModeAssociations) { + if (resource) { + this.configureFileAssociation(resource); } + return; + } - if (pick === galleryAction) { - galleryAction.run(); - return; - } + // User decided to configure settings for current language + if (pick === configureModeSettings) { + this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId)); + return; + } - // User decided to permanently configure associations, return right after - if (pick === configureModeAssociations) { - if (resource) { - this.configureFileAssociation(resource); - } - return; - } + // Change mode for active editor + const activeEditor = this.editorService.activeEditor; + if (activeEditor) { + const modeSupport = toEditorWithModeSupport(activeEditor); + if (modeSupport) { - // User decided to configure settings for current language - if (pick === configureModeSettings) { - this.preferencesService.configureSettingsForLanguage(modeId); - return; - } - - // Change mode for active editor - const activeEditor = this.editorService.activeEditor; - if (activeEditor) { - const modeSupport = toEditorWithModeSupport(activeEditor); - if (modeSupport) { - - // Find mode - let languageSelection: ILanguageSelection | undefined; - if (pick === autoDetectMode) { - if (textModel) { - const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); - } + // Find mode + let languageSelection: ILanguageSelection | undefined; + if (pick === autoDetectMode) { + if (textModel) { + const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); } - } else { - languageSelection = this.modeService.createByLanguageName(pick.label); } + } else { + languageSelection = this.modeService.createByLanguageName(pick.label); + } - // Change mode - if (typeof languageSelection !== 'undefined') { - modeSupport.setMode(languageSelection.languageIdentifier.language); - } + // Change mode + if (typeof languageSelection !== 'undefined') { + modeSupport.setMode(languageSelection.languageIdentifier.language); } } - }); + } } private configureFileAssociation(resource: URI): void { @@ -1171,7 +1170,7 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - run(): Promise { + async run(): Promise { if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } @@ -1196,93 +1195,88 @@ export class ChangeEncodingAction extends Action { reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' }; } - let pickActionPromise: Promise; + let action: IQuickPickItem; if (encodingSupport instanceof UntitledEditorInput) { - pickActionPromise = Promise.resolve(saveWithEncodingPick); + action = saveWithEncodingPick; } else if (!isWritableBaseEditor(activeControl)) { - pickActionPromise = Promise.resolve(reopenWithEncodingPick); + action = reopenWithEncodingPick; } else { - pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); } - return pickActionPromise.then(action => { - if (!action) { - return undefined; - } + if (!action) { + return; + } - const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + await timeout(50); // quick open is sensitive to being opened so soon after another - return timeout(50 /* quick open is sensitive to being opened so soon after another */) - .then(() => { - if (!resource || !this.fileService.canHandleResource(resource)) { - return Promise.resolve(null); // encoding detection only possible for resources the file service can handle - } + const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + if (!resource || !this.fileService.canHandleResource(resource)) { + return null; // encoding detection only possible for resources the file service can handle + } - return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null); - }) - .then((guessedEncoding: string) => { - const isReopenWithEncoding = (action === reopenWithEncodingPick); + const content = await this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }); + const guessedEncoding = content.encoding; - const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); + const isReopenWithEncoding = (action === reopenWithEncodingPick); - let directMatchIndex: number | undefined; - let aliasMatchIndex: number | undefined; + const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); - // All encodings are valid picks - const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) - .sort((k1, k2) => { - if (k1 === configuredEncoding) { - return -1; - } else if (k2 === configuredEncoding) { - return 1; - } + let directMatchIndex: number | undefined; + let aliasMatchIndex: number | undefined; - return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; - }) - .filter(k => { - if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { - return false; // do not show encoding if it is the guessed encoding that does not match the configured - } + // All encodings are valid picks + const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) + .sort((k1, k2) => { + if (k1 === configuredEncoding) { + return -1; + } else if (k2 === configuredEncoding) { + return 1; + } - return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode - }) - .map((key, index) => { - if (key === encodingSupport.getEncoding()) { - directMatchIndex = index; - } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { - aliasMatchIndex = index; - } + return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; + }) + .filter(k => { + if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { + return false; // do not show encoding if it is the guessed encoding that does not match the configured + } - return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; - }); + return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode + }) + .map((key, index) => { + if (key === encodingSupport.getEncoding()) { + directMatchIndex = index; + } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { + aliasMatchIndex = index; + } - const items = picks.slice() as IQuickPickItem[]; + return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; + }); - // If we have a guessed encoding, show it first unless it matches the configured encoding - if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { - picks.unshift({ type: 'separator' }); - picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); - } + const items = picks.slice() as IQuickPickItem[]; - return this.quickInputService.pick(picks, { - placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), - activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] - }).then(encoding => { - if (!encoding) { - return; - } + // If we have a guessed encoding, show it first unless it matches the configured encoding + if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { + picks.unshift({ type: 'separator' }); + picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); + } - const activeControl = this.editorService.activeControl; - if (!activeControl) { - return; - } - - const encodingSupport = toEditorWithEncodingSupport(activeControl.input); - if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) { - encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding - } - }); - }); + const encoding = await this.quickInputService.pick(picks, { + placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), + activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] }); + + if (!encoding) { + return; + } + + if (!this.editorService.activeControl) { + return; + } + + const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input); + if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { + activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding + } } } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 639c4ada7d85..99f284eb2dca 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -169,7 +169,7 @@ export class SideBySideEditor extends BaseEditor { } if (!this.detailsEditor || !this.masterEditor) { - return Promise.resolve(); + return; } await Promise.all([ diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 4a428b5cea0e..d4b3e4adef96 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { // Assert Model instance if (!(resolvedModel instanceof BaseTextEditorModel)) { - return Promise.reject(new Error('Unable to open file as text')); + throw new Error('Unable to open file as text'); } // Set Editor Model diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index c44249707e9a..e8a82735246c 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -523,7 +523,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - return Promise.resolve(undefined); + return; } // Support extra class from handler @@ -579,8 +579,8 @@ export class QuickOpenController extends Component implements IQuickOpenService return mapEntryToPath; } - private resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - let result = this._resolveHandler(handler); + private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + let result = this.doResolveHandler(handler); const id = handler.getId(); if (!this.handlerOnOpenCalled[id]) { @@ -594,14 +594,16 @@ export class QuickOpenController extends Component implements IQuickOpenService }); } - return result.then(null, (error) => { + try { + return await result; + } catch (error) { delete this.mapResolvedHandlersToPrefix[id]; - return Promise.reject(new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`)); - }); + throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`); + } } - private _resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise { const id = handler.getId(); // Return Cached diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index ffbee03d079b..1831daad7f64 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -191,7 +191,7 @@ export class SidebarPart extends CompositePart implements IViewletServi async openViewlet(id: string | undefined, focus?: boolean): Promise { if (typeof id === 'string' && this.getViewlet(id)) { - return Promise.resolve(this.doOpenViewlet(id, focus)); + return this.doOpenViewlet(id, focus); } await this.extensionService.whenInstalledExtensionsRegistered(); diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index b6691ee28f33..6d652b4e8cad 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -90,7 +90,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { ref.dispose(); this.modelReference = null; - return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); + throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 97733dddb2c8..9fa41b708455 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -18,7 +18,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; @@ -187,7 +186,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService); - delegatingEditorService.setEditorOpenHandler((group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { + delegatingEditorService.setEditorOpenHandler(async (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -203,16 +202,19 @@ export class ExplorerViewlet extends ViewContainerViewlet { openEditorsView.setStructuralRefreshDelay(delay); } - const onSuccessOrError = (editor: BaseEditor | null) => { + let openedEditor: IEditor | null = null; + try { + openedEditor = await this.editorService.openEditor(editor, options, group); + } catch (error) { + // ignore + } finally { const openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); } + } - return editor; - }; - - return this.editorService.openEditor(editor, options, group).then(onSuccessOrError, onSuccessOrError); + return openedEditor; }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService])); diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 99d47901672e..1161e7e0f575 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -243,27 +243,23 @@ export class ExplorerItem { return this.children.get(this.getPlatformAwareName(name)); } - fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { - let promise: Promise = Promise.resolve(undefined); + async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' const resolveMetadata = explorerService.sortOrder === 'modified'; - promise = fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }).then(stat => { - const resolved = ExplorerItem.create(stat, this); - ExplorerItem.mergeLocalWithDisk(resolved, this); - this._isDirectoryResolved = true; - }); + const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); + const resolved = ExplorerItem.create(stat, this); + ExplorerItem.mergeLocalWithDisk(resolved, this); + this._isDirectoryResolved = true; } - return promise.then(() => { - const items: ExplorerItem[] = []; - this.children.forEach(child => { - items.push(child); - }); - - return items; + const items: ExplorerItem[] = []; + this.children.forEach(child => { + items.push(child); }); + + return items; } /** diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 6bec4dbd5da3..204b23e1f02f 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -147,7 +147,7 @@ export class ExplorerService implements IExplorerService { return !!this.editable && (this.editable.stat === stat || !stat); } - select(resource: URI, reveal?: boolean): Promise { + async select(resource: URI, reveal?: boolean): Promise { const fileStat = this.findClosest(resource); if (fileStat) { this._onDidSelectResource.fire({ resource: fileStat.resource, reveal }); @@ -159,7 +159,9 @@ export class ExplorerService implements IExplorerService { const workspaceFolder = this.contextService.getWorkspaceFolder(resource); const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource; const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; - return this.fileService.resolve(rootUri, options).then(stat => { + + try { + const stat = await this.fileService.resolve(rootUri, options); // Convert to model const modelStat = ExplorerItem.create(stat, undefined, options.resolveTo); @@ -170,10 +172,10 @@ export class ExplorerService implements IExplorerService { // Select and Reveal this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal }); - }, () => { + } catch (error) { root.isError = true; this._onDidChangeItem.fire({ item: root, recursive: false }); - }); + } } refresh(): void { diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 5ee73f788d1a..8d72b3c257af 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -294,7 +294,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { this.onBeforeRun(this.commandId); // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { + setTimeout(async () => { if (action && (!(action instanceof Action) || action.enabled)) { try { /* __GDPR__ @@ -304,11 +304,17 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { } */ this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'quick open' }); - (action.run() || Promise.resolve()).then(() => { - if (action instanceof Action) { - action.dispose(); + + const promise = action.run(); + if (promise) { + try { + await promise; + } finally { + if (action instanceof Action) { + action.dispose(); + } } - }, err => this.onError(err)); + } } catch (error) { this.onError(error); } @@ -402,7 +408,7 @@ export class CommandsHandler extends QuickOpenHandler { this.commandHistoryEnabled = resolveCommandHistory(this.configurationService) > 0; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { if (this.waitedForExtensionsRegistered) { return this.doGetResults(searchValue, token); } @@ -411,11 +417,10 @@ export class CommandsHandler extends QuickOpenHandler { // a chance to register so that the complete set of commands shows up as result // We do not want to delay functionality beyond that time though to keep the commands // functional. - return Promise.race([timeout(800), this.extensionService.whenInstalledExtensionsRegistered().then(() => undefined)]).then(() => { - this.waitedForExtensionsRegistered = true; + await Promise.race([timeout(800).then(), this.extensionService.whenInstalledExtensionsRegistered()]); + this.waitedForExtensionsRegistered = true; - return this.doGetResults(searchValue, token); - }); + return this.doGetResults(searchValue, token); } private doGetResults(searchValue: string, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index bc97910fe3c2..ed5f3203846c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -495,7 +495,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { return this.cachedOutlineRequest; } - private doGetActiveOutline(): Promise { + private async doGetActiveOutline(): Promise { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (activeTextEditorWidget) { let model = activeTextEditorWidget.getModel(); @@ -504,13 +504,13 @@ export class GotoSymbolHandler extends QuickOpenHandler { } if (model && types.isFunction((model).getLanguageIdentifier)) { - return Promise.resolve(asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)).then(entries => { - return new OutlineModel(this.toQuickOpenEntries(entries)); - })); + const entries = await asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)); + + return new OutlineModel(this.toQuickOpenEntries(entries)); } } - return Promise.resolve(null); + return null; } decorateOutline(fullRange: IRange, startRange: IRange, editor: IEditor, group: IEditorGroup): void { diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 6599e4982e75..207662ab1ae3 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -156,12 +156,12 @@ export class OpenSymbolHandler extends QuickOpenHandler { return true; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { searchValue = searchValue.trim(); - let promise: Promise; + let entries: QuickOpenEntry[]; if (!this.options.skipDelay) { - promise = this.delayer.trigger(() => { + entries = await this.delayer.trigger(() => { if (token.isCancellationRequested) { return Promise.resolve([]); } @@ -169,32 +169,31 @@ export class OpenSymbolHandler extends QuickOpenHandler { return this.doGetResults(searchValue, token); }); } else { - promise = this.doGetResults(searchValue, token); + entries = await this.doGetResults(searchValue, token); } - return promise.then(e => new QuickOpenModel(e)); + return new QuickOpenModel(entries); } - private doGetResults(searchValue: string, token: CancellationToken): Promise { - return getWorkspaceSymbols(searchValue, token).then(tuples => { - if (token.isCancellationRequested) { - return []; - } + private async doGetResults(searchValue: string, token: CancellationToken): Promise { + const tuples = await getWorkspaceSymbols(searchValue, token); + if (token.isCancellationRequested) { + return []; + } - const result: SymbolEntry[] = []; - for (let tuple of tuples) { - const [provider, bearings] = tuple; - this.fillInSymbolEntries(result, provider, bearings, searchValue); - } + const result: SymbolEntry[] = []; + for (let tuple of tuples) { + const [provider, bearings] = tuple; + this.fillInSymbolEntries(result, provider, bearings, searchValue); + } - // Sort (Standalone only) - if (!this.options.skipSorting) { - searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; - return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); - } + // Sort (Standalone only) + if (!this.options.skipSorting) { + searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; + return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); + } - return result; - }); + return result; } private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 40b009039a22..54eb96b7bc32 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -230,7 +230,7 @@ class CodeRendererMain extends Disposable { // Multi-root workspace if (this.configuration.workspace) { - return Promise.resolve(this.configuration.workspace); + return this.configuration.workspace; } // Single-folder workspace @@ -260,7 +260,7 @@ class CodeRendererMain extends Disposable { // Return early the folder is not local if (folderUri.scheme !== Schemas.file) { - return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }); + return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }; } function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string { diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 850fe4b103a1..624ec71cad0e 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -352,7 +352,7 @@ class BackupFileServiceImpl implements IBackupFileService { export class InMemoryBackupFileService implements IBackupFileService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private backups: Map = new Map(); diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index baeccb314dd0..9313c53ccb9a 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -39,10 +39,11 @@ export class JSONEditingService implements IJSONEditingService { return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, value, save))); // queue up writes to prevent race conditions } - private doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { - return this.resolveAndValidate(resource, save) - .then(reference => this.writeToBuffer(reference.object.textEditorModel, value) - .then(() => reference.dispose())); + private async doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { + const reference = await this.resolveAndValidate(resource, save); + await this.writeToBuffer(reference.object.textEditorModel, value); + + reference.dispose(); } private async writeToBuffer(model: ITextModel, value: IJSONValue): Promise { @@ -97,21 +98,21 @@ export class JSONEditingService implements IJSONEditingService { return parseErrors.length > 0; } - private resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { - return this.resolveModelReference(resource) - .then(reference => { - const model = reference.object.textEditorModel; + private async resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { + const reference = await this.resolveModelReference(resource); - if (this.hasParseErrors(model)) { - return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); - } + const model = reference.object.textEditorModel; - // Target cannot be dirty if not writing into buffer - if (checkDirty && this.textFileService.isDirty(resource)) { - return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); - } - return reference; - }); + if (this.hasParseErrors(model)) { + return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); + } + + // Target cannot be dirty if not writing into buffer + if (checkDirty && this.textFileService.isDirty(resource)) { + return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); + } + + return reference; } private reject(code: JSONEditingErrorCode): Promise { diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 9ff5db4854a9..c5792b4a9043 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -86,6 +86,7 @@ export class FileDialogService implements IFileDialogService { private shouldUseSimplified(schema: string): boolean { const setting = this.configurationService.getValue('files.simpleDialog.enable'); + return (schema !== Schemas.file) || (setting === true); } @@ -93,7 +94,7 @@ export class FileDialogService implements IFileDialogService { return schema !== Schemas.file ? [schema, Schemas.file] : [schema]; } - pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -103,21 +104,23 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return (this.fileService.resolve(uri)).then(stat => { - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); - }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + + if (uri) { + const stat = await this.fileService.resolve(uri); + + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFileAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -127,18 +130,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFile.title', 'Open File'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -148,18 +152,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFolder.title', 'Open Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -170,12 +175,13 @@ export class FileDialogService implements IFileDialogService { const title = nls.localize('openWorkspace.title', 'Open Workspace'); const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); @@ -190,33 +196,34 @@ export class FileDialogService implements IFileDialogService { }; } - showSaveDialog(options: ISaveDialogOptions): Promise { + async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow saving in the own file system } + return this.saveRemoteResource(options); } - return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { - if (result) { - return URI.file(result); - } + const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result) { + return URI.file(result); + } - return undefined; - }); + return; } - showOpenDialog(options: IOpenDialogOptions): Promise { + async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow loading in the own file system } - return this.pickRemoteResource(options).then(uri => { - return uri ? [uri] : undefined; - }); + + const uri = await this.pickRemoteResource(options); + + return uri ? [uri] : undefined; } const defaultUri = options.defaultUri; @@ -243,16 +250,20 @@ export class FileDialogService implements IFileDialogService { newOptions.properties!.push('multiSelections'); } - return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); + const result = await this.windowService.showOpenDialog(newOptions); + + return result ? result.map(URI.file) : undefined; } private pickRemoteResource(options: IOpenDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showOpenDialog(options); } private saveRemoteResource(options: ISaveDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showSaveDialog(options); } @@ -263,7 +274,6 @@ export class FileDialogService implements IFileDialogService { private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow(); } - } function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 584d5ce40314..683b344b9ddb 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -650,18 +650,17 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } - return this.editorOpenHandler(group, editor, options).then(control => { - if (control) { - return control; // the opening was handled, so return early - } + const control = await this.editorOpenHandler(group, editor, options); + if (control) { + return control; // the opening was handled, so return early + } - return super.doOpenEditor(group, editor, options); - }); + return super.doOpenEditor(group, editor, options); } } diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index cf5291ec6e9f..8631b0191337 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -78,7 +78,7 @@ export class FileService extends Disposable implements IFileService { }); if (this.provider.has(scheme)) { - return Promise.resolve(); // provider is already here so we can return directly + return; // provider is already here so we can return directly } // If the provider is not yet there, make sure to join on the listeners assuming @@ -235,7 +235,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - return Promise.resolve(fileStat); + return fileStat; } async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise; diff --git a/src/vs/workbench/services/files/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts index e62697f3a8ba..a3f44904c3db 100644 --- a/src/vs/workbench/services/files/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -447,8 +447,7 @@ suite('Disk File Service', () => { await service.del(source.resource); return Promise.reject(new Error('Unexpected')); - } - catch (error) { + } catch (error) { return Promise.resolve(true); } }); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c114ccbe3b93..4da4619b9e9c 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -144,7 +144,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private onFileChanges(e: FileChangesEvent): void { + private async onFileChanges(e: FileChangesEvent): Promise { let fileEventImpactsModel = false; let newInOrphanModeGuess: boolean | undefined; @@ -167,28 +167,25 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) { - let checkOrphanedPromise: Promise; + let newInOrphanModeValidated: boolean = false; if (newInOrphanModeGuess) { // We have received reports of users seeing delete events even though the file still // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). // Since we do not want to mark the model as orphaned, we have to check if the // file is really gone and not just a faulty file event. - checkOrphanedPromise = timeout(100).then(() => { - if (this.disposed) { - return true; - } + await timeout(100); - return this.fileService.exists(this.resource).then(exists => !exists); - }); - } else { - checkOrphanedPromise = Promise.resolve(false); + if (this.disposed) { + newInOrphanModeValidated = true; + } else { + const exists = await this.fileService.exists(this.resource); + newInOrphanModeValidated = !exists; + } } - checkOrphanedPromise.then(newInOrphanModeValidated => { - if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { - this.setOrphaned(newInOrphanModeValidated); - } - }); + if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { + this.setOrphaned(newInOrphanModeValidated); + } } } @@ -239,13 +236,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.backupResource(target, this.createSnapshot(), this.versionId, meta); } - - return Promise.resolve(); } async revert(soft?: boolean): Promise { if (!this.isResolved()) { - return Promise.resolve(undefined); + return; } // Cancel any running auto-save @@ -254,25 +249,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const undo = this.setDirty(false); - let loadPromise: Promise; - if (soft) { - loadPromise = Promise.resolve(); - } else { - loadPromise = this.load({ forceReadFromDisk: true }); + // Force read from disk unless reverting soft + if (!soft) { + try { + await this.load({ forceReadFromDisk: true }); + } catch (error) { + + // Set flags back to previous values, we are still dirty if revert failed + undo(); + + throw error; + } } - try { - await loadPromise; - - // Emit file change event - this._onDidStateChange.fire(StateChange.REVERTED); - } catch (error) { - - // Set flags back to previous values, we are still dirty if revert failed - undo(); - - return Promise.reject(error); - } + // Emit file change event + this._onDidStateChange.fire(StateChange.REVERTED); } async load(options?: ILoadOptions): Promise { @@ -600,7 +591,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil save(options: ISaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { - return Promise.resolve(undefined); + return Promise.resolve(); } this.logService.trace('save() - enter', this.resource); @@ -626,7 +617,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.saveSequentializer.hasPendingSave(versionId)) { this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource); - return this.saveSequentializer.pendingSave || Promise.resolve(undefined); + return this.saveSequentializer.pendingSave || Promise.resolve(); } // Return early if not dirty (unless forced) or version changed meanwhile @@ -639,7 +630,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if ((!options.force && !this.dirty) || versionId !== this.versionId) { this.logService.trace(`doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource); - return Promise.resolve(undefined); + return Promise.resolve(); } // Return if currently saving by storing this save request as the next save that should happen. diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index ade71f748cdc..a2f3269ecd0c 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -151,10 +151,10 @@ export class TextModelResolverService implements ITextModelService { const cachedModel = this.modelService.getModel(resource); if (!cachedModel) { - return Promise.reject(new Error('Cant resolve inmemory resource')); + throw new Error('Cant resolve inmemory resource'); } - return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel)); + return new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel); } const ref = this.resourceModelCollection.acquire(resource.toString()); diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index b1c60ab4252b..f7324b042a11 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -67,74 +67,81 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } - private saveUntitedBeforeShutdown(reason: ShutdownReason): Promise | undefined { + private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise { if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { - return undefined; // only interested when window is closing or loading + return false; // 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 + return false; // only care about untitled workspaces to ask for saving } - return this.windowsService.getWindowCount().then(windowCount => { - if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { - return false; // Windows/Linux: quits when last window is closed, so do not ask then - } - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } + const windowCount = await this.windowsService.getWindowCount(); - const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { + return false; // Windows/Linux: quits when last window is closed, so do not ask then + } - const buttons: { label: string; result: ConfirmResult; }[] = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } + enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL + } - const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); - const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); - const cancelId = buttons.indexOf(cancel); + const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - return this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }).then(res => { - switch (buttons[res].result) { + const buttons: { label: string; result: ConfirmResult; }[] = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; + const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); + const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); + const cancelId = buttons.indexOf(cancel); - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; + const res = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }); - // Save: save workspace, but do not veto unload - case ConfirmResult.SAVE: { - return this.pickNewWorkspacePath().then(newWorkspacePath => { - if (newWorkspacePath) { - return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { - return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { - const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); - this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; - }); - }, () => false); - } - return true; // keep veto if no target was provided - }); - } + switch (buttons[res].result) { + + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; + + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + + // Save: save workspace, but do not veto unload if path provided + case ConfirmResult.SAVE: { + const newWorkspacePath = await this.pickNewWorkspacePath(); + if (!newWorkspacePath) { + return true; // keep veto if no target was provided } - }); - }); + + try { + await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); + + const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath); + + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); + this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + } catch (error) { + // ignore + } + + return false; + } + } } pickNewWorkspacePath(): Promise { @@ -218,7 +225,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri)); if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) { - return Promise.resolve(); // return if the operation is a no-op for the current state + return; // return if the operation is a no-op for the current state } return this.createAndEnterWorkspace(newWorkspaceFolders); @@ -267,7 +274,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { if (path && !await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const remoteAuthority = this.environmentService.configuration.remoteAuthority; const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); @@ -281,11 +288,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async saveAndEnterWorkspace(path: URI): Promise { if (!await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier) { - return Promise.reject(null); + return; } await this.saveWorkspaceAs(workspaceIdentifier, path); @@ -318,7 +325,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Return early if target is same as source if (isEqual(configPathURI, targetConfigPathURI)) { - return Promise.resolve(null); + return; } // Read the contents of the workspace file, update it to new location and save it. @@ -327,18 +334,17 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); } - private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise { + private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void { switch (error.code) { case JSONEditingErrorCode.ERROR_INVALID_FILE: this.onInvalidWorkspaceConfigurationFileError(); - return Promise.resolve(); + break; case JSONEditingErrorCode.ERROR_FILE_DIRTY: this.onWorkspaceConfigurationFileDirtyError(); - return Promise.resolve(); + break; + default: + this.notificationService.error(error.message); } - this.notificationService.error(error.message); - - return Promise.resolve(); } private onInvalidWorkspaceConfigurationFileError(): void { @@ -362,7 +368,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async enterWorkspace(path: URI): Promise { if (!!this.environmentService.extensionTestsLocationURI) { - return Promise.reject(new Error('Entering a new workspace is not possible in tests.')); + throw new Error('Entering a new workspace is not possible in tests.'); } const workspace = await this.workspaceService.getWorkspaceIdentifier(path);