Skip to content

Commit

Permalink
add formatter conflict information (and ask to configure default) as …
Browse files Browse the repository at this point in the history
…language status item, #148053
  • Loading branch information
jrieken authored and aeschli committed May 2, 2022
1 parent 9d6ada6 commit 97dc95c
Showing 1 changed file with 80 additions and 29 deletions.
109 changes: 80 additions & 29 deletions src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider } from 'vs/editor/common/languages';
import * as nls from 'vs/nls';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { formatDocumentRangesWithProvider, formatDocumentWithProvider, getRealAndSyntheticDocumentFormattersOrdered, FormattingConflicts, FormattingMode } from 'vs/editor/contrib/format/browser/format';
import { Range } from 'vs/editor/common/core/range';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
Expand All @@ -21,7 +21,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextModel } from 'vs/editor/common/model';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
Expand All @@ -30,6 +30,10 @@ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/exte
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { generateUuid } from 'vs/base/common/uuid';

type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider;

Expand All @@ -41,6 +45,8 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
static extensionItemLabels: string[] = [];
static extensionDescriptions: string[] = [];

private readonly _languageStatusStore = this._store.add(new DisposableStore());

constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
Expand All @@ -49,10 +55,16 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
@IDialogService private readonly _dialogService: IDialogService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@ILanguageService private readonly _languageService: ILanguageService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService,
@IEditorService private readonly _editorService: IEditorService,
) {
super();
this._register(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this));
this._register(FormattingConflicts.setFormatterSelector((formatter, document, mode) => this._selectFormatter(formatter, document, mode)));
this._store.add(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this));
this._store.add(FormattingConflicts.setFormatterSelector((formatter, document, mode) => this._selectFormatter(formatter, document, mode)));
this._store.add(_editorService.onDidActiveEditorChange(this._updateStatus, this));
this._store.add(_languageFeaturesService.documentFormattingEditProvider.onDidChange(this._updateStatus, this));
this._store.add(_languageFeaturesService.documentRangeFormattingEditProvider.onDidChange(this._updateStatus, this));
this._updateConfigValues();
}

Expand Down Expand Up @@ -93,8 +105,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
return s.match(/\s/) ? `'${s}'` : s;
}

private async _selectFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined> {

private async _analyzeFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel): Promise<T | string> {
const defaultFormatterId = this._configService.getValue<string>(DefaultFormatter.configName, {
resource: document.uri,
overrideIdentifier: document.getLanguageId()
Expand All @@ -114,50 +125,45 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
// formatter does not target this file
const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId();
const detail = nls.localize('miss', "Extension '{0}' is configured as formatter but it cannot format '{1}'-files", extension.displayName || extension.name, langName);
if (mode === FormattingMode.Silent) {
this._notificationService.status(detail, { hideAfter: 4000 });
return undefined;
} else {
const result = await this._dialogService.confirm({
message: nls.localize('miss.1', "Change Default Formatter"),
detail,
primaryButton: nls.localize('do.config', "Configure..."),
secondaryButton: nls.localize('cancel', "Cancel")
});
if (result.confirmed) {
return this._pickAndPersistDefaultFormatter(formatter, document);
} else {
return undefined;
}
}
return detail;
}

} else if (formatter.length === 1) {
// ok -> nothing configured but only one formatter available
return formatter[0];
}

const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId();
const message = !defaultFormatterId
? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName))
? nls.localize('config.needed', "There are multiple formatters for '{0}' files. One of them should be configured as default formatter.", DefaultFormatter._maybeQuotes(langName))
: nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId);

return message;
}

private async _selectFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined> {
const formatterOrMessage = await this._analyzeFormatter(formatter, document);
if (typeof formatterOrMessage !== 'string') {
return formatterOrMessage;
}

if (mode !== FormattingMode.Silent) {
// running from a user action -> show modal dialog so that users configure
// a default formatter
const result = await this._dialogService.confirm({
message,
message: nls.localize('miss.1', "Configure Default Formatter"),
detail: formatterOrMessage,
primaryButton: nls.localize('do.config', "Configure..."),
secondaryButton: nls.localize('cancel', "Cancel")
});
if (result.confirmed) {
return this._pickAndPersistDefaultFormatter(formatter, document);
}

} else {
// no user action -> show a silent notification and proceed
this._notificationService.prompt(
Severity.Info,
message,
formatterOrMessage,
[{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document) }],
{ silent: true }
);
Expand All @@ -184,6 +190,51 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
});
return formatter[pick.index];
}

// --- status item

private _updateStatus() {
this._languageStatusStore.clear();

const editor = getCodeEditor(this._editorService.activeTextEditorControl);
if (!editor || !editor.hasModel()) {
return;
}


const document = editor.getModel();
const formatter = getRealAndSyntheticDocumentFormattersOrdered(this._languageFeaturesService.documentFormattingEditProvider, this._languageFeaturesService.documentRangeFormattingEditProvider, document);

if (formatter.length === 0) {
return;
}

const cts = new CancellationTokenSource();
this._languageStatusStore.add(toDisposable(() => cts.dispose(true)));

this._analyzeFormatter(formatter, document).then(result => {
if (cts.token.isCancellationRequested) {
return;
}
if (typeof result !== 'string') {
return;
}
const command = { id: `formatter/configure/dfl/${generateUuid()}`, title: nls.localize('do.config', "Configure...") };
this._languageStatusStore.add(CommandsRegistry.registerCommand(command.id, () => this._pickAndPersistDefaultFormatter(formatter, document)));
this._languageStatusStore.add(this._languageStatusService.addStatus({
id: 'formatter.conflict',
name: nls.localize('summary', "Formatter Conflicts"),
selector: { language: document.getLanguageId(), pattern: document.uri.fsPath },
severity: Severity.Error,
label: nls.localize('formatter', "Formatting"),
detail: result,
busy: false,
source: '',
command,
accessibilityInfo: undefined
}));
});
}
}

Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
Expand Down

0 comments on commit 97dc95c

Please sign in to comment.