diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index bf7d5c6660d..62282e00c5f 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue, sequence } from 'vs/base/common/async'; +import { IdleValue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import * as strings from 'vs/base/common/strings'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -23,7 +23,7 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -33,11 +33,21 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; +import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export interface ICodeActionsOnSaveOptions { [kind: string]: boolean; } +class SaveParticipantError extends Error { + constructor(message: string, readonly setting?: string) { + super(message); + } +} + export interface ISaveParticipantParticipant extends ISaveParticipant { // progressMessage: string; } @@ -233,7 +243,10 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { const request = this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, source.token); setTimeout(() => { - reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout)); + reject(new SaveParticipantError( + localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout), + 'editor.formatOnSaveTimeout' + )); source.cancel(); }, timeout); @@ -295,7 +308,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { new Promise((_resolve, reject) => setTimeout(() => { tokenSource.cancel(); - reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)); + reject(new SaveParticipantError( + localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout), + 'editor.codeActionsOnSaveTimeout' + )); }, timeout)), this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token) ]).finally(() => { @@ -347,7 +363,10 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { } return new Promise((resolve, reject) => { - setTimeout(() => reject(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms")), 1750); + setTimeout( + () => reject(new SaveParticipantError(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))), + 1750 + ); this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => { if (!values.every(success => success)) { return Promise.reject(new Error('listener failed')); @@ -368,6 +387,7 @@ export class SaveParticipant implements ISaveParticipant { extHostContext: IExtHostContext, @IInstantiationService instantiationService: IInstantiationService, @IProgressService private readonly _progressService: IProgressService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, @ILogService private readonly _logService: ILogService ) { this._saveParticipants = new IdleValue(() => [ @@ -388,12 +408,54 @@ export class SaveParticipant implements ISaveParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - return this._progressService.withProgress({ location: ProgressLocation.Window }, progress => { + return this._progressService.withProgress({ location: ProgressLocation.Window }, async progress => { progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); - const promiseFactory = this._saveParticipants.getValue().map(p => () => { - return p.participate(model, env); - }); - return sequence(promiseFactory).then(() => { }, err => this._logService.warn(err)); + + let firstError: SaveParticipantError | undefined; + + for (let p of this._saveParticipants.getValue()) { + try { + await p.participate(model, env); + } catch (err) { + this._logService.warn(err); + firstError = !firstError && err instanceof SaveParticipantError ? err : firstError; + } + } + + if (firstError) { + this._showParticipantError(firstError); + } }); } + + private _showParticipantError(err: SaveParticipantError): void { + + let entry: any = { + text: localize('title', "$(error) Save Participants Failed: {0}", err.message) + }; + if (err.setting) { + entry.command = '_showSettings'; + entry.arguments = [err.setting]; + } + + const handle = this._statusbarService.addEntry( + entry, + 'saveParticipants.error', + localize('status.message', "Save Participants Errors"), + StatusbarAlignment.LEFT, + -Number.MAX_VALUE /* far right on left hand side */ + ); + + setTimeout(() => handle.dispose(), 5000); + } } + +CommandsRegistry.registerCommand('_showSettings', (accessor, ...args: any[]) => { + const [setting] = args; + const control = accessor.get(IEditorService).activeControl as SettingsEditor2; + if (control instanceof SettingsEditor2) { + control.focusSearch(`@tag:usesOnlineServices`); + } else { + accessor.get(IPreferencesService).openSettings(false, setting); + } +});