mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 09:10:30 -04:00
Merge from vscode f5044f0910e4aa7e7e06cb509781f3d56e729959 (#4759)
This commit is contained in:
@@ -3,6 +3,5 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/format.contribution';
|
||||
import './formatActionsMultiple';
|
||||
import './formatActionsNone';
|
||||
|
||||
@@ -6,28 +6,165 @@
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { DocumentRangeFormattingEditProviderRegistry, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider } from 'vs/editor/common/modes';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
|
||||
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 { formatDocumentRangeWithProvider, formatDocumentWithProvider, getRealAndSyntheticDocumentFormattersOrdered } from 'vs/editor/contrib/format/format';
|
||||
import { formatDocumentRangeWithProvider, formatDocumentWithProvider, getRealAndSyntheticDocumentFormattersOrdered, FormattingConflicts, FormattingMode } from 'vs/editor/contrib/format/format';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { showExtensionQuery } from 'vs/workbench/contrib/format/browser/showExtensionQuery';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { Disposable } 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';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
|
||||
type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider;
|
||||
|
||||
class DefaultFormatter extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static configName = 'editor.defaultFormatter';
|
||||
|
||||
static extensionIds: string[] = [];
|
||||
static extensionDescriptions: string[] = [];
|
||||
|
||||
constructor(
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
) {
|
||||
super();
|
||||
this._register(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this));
|
||||
this._register(FormattingConflicts.setFormatterSelector((formatter, document, mode) => this._selectFormatter(formatter, document, mode)));
|
||||
this._updateConfigValues();
|
||||
}
|
||||
|
||||
private async _updateConfigValues(): Promise<void> {
|
||||
const extensions = await this._extensionService.getExtensions();
|
||||
|
||||
DefaultFormatter.extensionIds.length = 0;
|
||||
DefaultFormatter.extensionDescriptions.length = 0;
|
||||
for (const extension of extensions) {
|
||||
if (extension.main) {
|
||||
DefaultFormatter.extensionIds.push(extension.identifier.value);
|
||||
DefaultFormatter.extensionDescriptions.push(extension.description || '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static _maybeQuotes(s: string): string {
|
||||
return s.match(/\s/) ? `'${s}'` : s;
|
||||
}
|
||||
|
||||
private async _selectFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined> {
|
||||
|
||||
const defaultFormatterId = this._configService.getValue<string>(DefaultFormatter.configName, {
|
||||
resource: document.uri,
|
||||
overrideIdentifier: document.getModeId()
|
||||
});
|
||||
|
||||
if (defaultFormatterId) {
|
||||
// good -> formatter configured
|
||||
const [defaultFormatter] = formatter.filter(formatter => ExtensionIdentifier.equals(formatter.extensionId, defaultFormatterId));
|
||||
if (defaultFormatter) {
|
||||
// formatter available
|
||||
return defaultFormatter;
|
||||
|
||||
} else {
|
||||
// formatter gone
|
||||
const extension = await this._extensionService.getExtension(defaultFormatterId);
|
||||
const label = this._labelService.getUriLabel(document.uri, { relative: true });
|
||||
const message = extension
|
||||
? nls.localize('miss', "Extension '{0}' cannot format '{1}'", extension.displayName || extension.name, label)
|
||||
: nls.localize('gone', "Extension '{0}' is configured as formatter but not available", defaultFormatterId);
|
||||
this._statusbarService.setStatusMessage(message, 4000);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
} else if (formatter.length === 1) {
|
||||
// ok -> nothing configured but only one formatter available
|
||||
return formatter[0];
|
||||
}
|
||||
|
||||
const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId();
|
||||
const silent = mode === FormattingMode.Silent;
|
||||
const message = nls.localize('config.needed', "There are multiple formatters for {0}-files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName));
|
||||
|
||||
return new Promise<T | undefined>((resolve, reject) => {
|
||||
this._notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
[{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document).then(resolve, reject) }],
|
||||
{ silent, onCancel: resolve }
|
||||
);
|
||||
|
||||
if (silent) {
|
||||
// don't wait when formatting happens without interaction
|
||||
// but pick some formatter...
|
||||
resolve(formatter[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _pickAndPersistDefaultFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel): Promise<T | undefined> {
|
||||
const picks = formatter.map((formatter, index) => {
|
||||
return <IIndexedPick>{
|
||||
index,
|
||||
label: formatter.displayName || formatter.extensionId || '?'
|
||||
};
|
||||
});
|
||||
const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId();
|
||||
const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) });
|
||||
if (!pick || !formatter[pick.index].extensionId) {
|
||||
return undefined;
|
||||
}
|
||||
this._configService.updateValue(DefaultFormatter.configName, formatter[pick.index].extensionId!.value, {
|
||||
resource: document.uri,
|
||||
overrideIdentifier: document.getModeId()
|
||||
});
|
||||
return formatter[pick.index];
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
|
||||
DefaultFormatter,
|
||||
LifecyclePhase.Restored
|
||||
);
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
|
||||
id: 'editor',
|
||||
order: 5,
|
||||
type: 'object',
|
||||
overridable: true,
|
||||
properties: {
|
||||
[DefaultFormatter.configName]: {
|
||||
description: nls.localize('formatter.default', "Defines a default formatter which takes precedence over all other formatter settings. Must be the identifier of an extension contributing a formatter."),
|
||||
type: 'string',
|
||||
default: null,
|
||||
enum: DefaultFormatter.extensionIds,
|
||||
markdownEnumDescriptions: DefaultFormatter.extensionDescriptions
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
interface IIndexedPick extends IQuickPickItem {
|
||||
index: number;
|
||||
}
|
||||
|
||||
const openExtensionAction: IQuickInputButton = {
|
||||
tooltip: nls.localize('show.ext', "Show extension..."),
|
||||
iconClass: 'format-show-extension'
|
||||
};
|
||||
|
||||
function logFormatterTelemetry<T extends { extensionId?: ExtensionIdentifier }>(telemetryService: ITelemetryService, mode: 'document' | 'range', options: T[], pick?: T) {
|
||||
|
||||
function extKey(obj: T): string {
|
||||
@@ -48,6 +185,47 @@ function logFormatterTelemetry<T extends { extensionId?: ExtensionIdentifier }>(
|
||||
});
|
||||
}
|
||||
|
||||
async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, formatters: FormattingEditProvider[]): Promise<number | undefined> {
|
||||
const quickPickService = accessor.get(IQuickInputService);
|
||||
const configService = accessor.get(IConfigurationService);
|
||||
const modeService = accessor.get(IModeService);
|
||||
|
||||
const overrides = { resource: model.uri, overrideIdentifier: model.getModeId() };
|
||||
const defaultFormatter = configService.getValue<string>(DefaultFormatter.configName, overrides);
|
||||
|
||||
const picks = formatters.map((provider, index) => {
|
||||
return <IIndexedPick>{
|
||||
index,
|
||||
label: provider.displayName || '',
|
||||
description: ExtensionIdentifier.equals(provider.extensionId, defaultFormatter) ? nls.localize('def', "(default)") : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
const configurePick: IQuickPickItem = {
|
||||
label: nls.localize('config', "Configure Default Formatter...")
|
||||
};
|
||||
|
||||
const pick = await quickPickService.pick([...picks, { type: 'separator' }, configurePick], { placeHolder: nls.localize('format.placeHolder', "Select a formatter") });
|
||||
if (!pick) {
|
||||
// dismissed
|
||||
return undefined;
|
||||
|
||||
} else if (pick === configurePick) {
|
||||
// config default
|
||||
const langName = modeService.getLanguageName(model.getModeId()) || model.getModeId();
|
||||
const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) });
|
||||
if (pick && formatters[pick.index].extensionId) {
|
||||
configService.updateValue(DefaultFormatter.configName, formatters[pick.index].extensionId!.value, overrides);
|
||||
}
|
||||
return undefined;
|
||||
|
||||
} else {
|
||||
// picked one
|
||||
return (<IIndexedPick>pick).index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerEditorAction(class FormatDocumentMultipleAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -68,32 +246,14 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction {
|
||||
return;
|
||||
}
|
||||
const instaService = accessor.get(IInstantiationService);
|
||||
const quickPickService = accessor.get(IQuickInputService);
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
const model = editor.getModel();
|
||||
|
||||
const provider = getRealAndSyntheticDocumentFormattersOrdered(model);
|
||||
const picks = provider.map((provider, index) => {
|
||||
return <IIndexedPick>{
|
||||
index,
|
||||
label: provider.displayName || '',
|
||||
buttons: [openExtensionAction]
|
||||
};
|
||||
});
|
||||
|
||||
const pick = await quickPickService.pick(picks, {
|
||||
placeHolder: nls.localize('format.placeHolder', "Select a formatter"),
|
||||
onDidTriggerItemButton: (e) => {
|
||||
const { extensionId } = provider[e.item.index];
|
||||
return showExtensionQuery(viewletService, `@id:${extensionId!.value}`);
|
||||
}
|
||||
});
|
||||
const pick = await instaService.invokeFunction(showFormatterPick, model, provider);
|
||||
if (pick) {
|
||||
await instaService.invokeFunction(formatDocumentWithProvider, provider[pick.index], editor, CancellationToken.None);
|
||||
await instaService.invokeFunction(formatDocumentWithProvider, provider[pick], editor, CancellationToken.None);
|
||||
}
|
||||
|
||||
logFormatterTelemetry(telemetryService, 'document', provider, pick && provider[pick.index]);
|
||||
logFormatterTelemetry(telemetryService, 'document', provider, typeof pick === 'number' && provider[pick] || undefined);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -118,36 +278,20 @@ registerEditorAction(class FormatSelectionMultipleAction extends EditorAction {
|
||||
return;
|
||||
}
|
||||
const instaService = accessor.get(IInstantiationService);
|
||||
const quickPickService = accessor.get(IQuickInputService);
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
const model = editor.getModel();
|
||||
|
||||
const model = editor.getModel();
|
||||
let range: Range = editor.getSelection();
|
||||
if (range.isEmpty()) {
|
||||
range = new Range(range.startLineNumber, 1, range.startLineNumber, model.getLineMaxColumn(range.startLineNumber));
|
||||
}
|
||||
|
||||
const provider = DocumentRangeFormattingEditProviderRegistry.ordered(model);
|
||||
const picks = provider.map((provider, index) => {
|
||||
return <IIndexedPick>{
|
||||
index,
|
||||
label: provider.displayName || '',
|
||||
buttons: [openExtensionAction]
|
||||
};
|
||||
});
|
||||
|
||||
const pick = await quickPickService.pick(picks, {
|
||||
placeHolder: nls.localize('format.placeHolder', "Select a formatter"),
|
||||
onDidTriggerItemButton: (e) => {
|
||||
const { extensionId } = provider[e.item.index];
|
||||
return showExtensionQuery(viewletService, `@id:${extensionId!.value}`);
|
||||
}
|
||||
});
|
||||
const pick = await instaService.invokeFunction(showFormatterPick, model, provider);
|
||||
if (pick) {
|
||||
await instaService.invokeFunction(formatDocumentRangeWithProvider, provider[pick.index], editor, range, CancellationToken.None);
|
||||
await instaService.invokeFunction(formatDocumentRangeWithProvider, provider[pick], editor, range, CancellationToken.None);
|
||||
}
|
||||
|
||||
logFormatterTelemetry(telemetryService, 'range', provider, pick && provider[pick.index]);
|
||||
logFormatterTelemetry(telemetryService, 'range', provider, typeof pick === 'number' && provider[pick] || undefined);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#C5C5C5"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 927 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#424242"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 927 B |
@@ -1,13 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .format-show-extension {
|
||||
background-image: url('configure.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .format-show-extension,
|
||||
.hc-black .monaco-workbench .format-show-extension {
|
||||
background-image: url('configure-inverse.svg');
|
||||
}
|
||||
Reference in New Issue
Block a user