From 6e76e0e1b79e3c546e12da2eafbbee464767e00c Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 23 Oct 2022 15:45:34 +0200 Subject: [PATCH] feat: select the folding provider to use (#157434) * feat: support extension id in property `editor.foldingStrategy` * work in progress * use new setting 'editor.defaultFoldingRangeProvider' defined in workspace * revert editorOptions changes Co-authored-by: Martin Aeschlimann --- src/vs/editor/common/languages.ts | 5 + .../editor/contrib/folding/browser/folding.ts | 18 +++- .../api/browser/mainThreadLanguageFeatures.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 2 +- .../folding/browser/folding.contribution.ts | 93 +++++++++++++++++++ 6 files changed, 116 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 2cae627b585..0d25cd047b4 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1336,6 +1336,11 @@ export interface FoldingContext { */ export interface FoldingRangeProvider { + /** + * @internal + */ + readonly id?: string; + /** * An optional event to signal that the folding ranges from this provider have changed. */ diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index 4b6e24e48cd..370d424b656 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -7,7 +7,7 @@ import { CancelablePromise, createCancelablePromise, Delayer, RunOnceScheduler } import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import 'vs/css!./folding'; @@ -21,7 +21,7 @@ import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { FoldingRangeKind } from 'vs/editor/common/languages'; +import { FoldingRangeKind, FoldingRangeProvider } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeModel'; @@ -65,6 +65,8 @@ export interface FoldingLimitInfo { limited: number | false; } +export type FoldingRangeProviderSelector = (formatter: FoldingRangeProvider[], document: ITextModel) => FoldingRangeProvider[] | undefined; + export class FoldingController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.folding'; @@ -73,6 +75,13 @@ export class FoldingController extends Disposable implements IEditorContribution return editor.getContribution(FoldingController.ID); } + private static _foldingRangeSelector: FoldingRangeProviderSelector | undefined; + + public static setFoldingRangeProviderSelector(foldingRangeSelector: FoldingRangeProviderSelector): IDisposable { + FoldingController._foldingRangeSelector = foldingRangeSelector; + return { dispose: () => { FoldingController._foldingRangeSelector = undefined; } }; + } + private readonly editor: ICodeEditor; private _isEnabled: boolean; private _useFoldingProviders: boolean; @@ -282,8 +291,9 @@ export class FoldingController extends Disposable implements IEditorContribution this.rangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._foldingLimitReporter); // fallback if (this._useFoldingProviders && this.foldingModel) { const foldingProviders = this.languageFeaturesService.foldingRangeProvider.ordered(this.foldingModel.textModel); - if (foldingProviders.length > 0) { - this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.triggerFoldingModelChanged(), this._foldingLimitReporter); + const selectedProviders = (FoldingController._foldingRangeSelector?.(foldingProviders, editorModel)) ?? foldingProviders; + if (selectedProviders.length > 0) { + this.rangeProvider = new SyntaxRangeProvider(editorModel, selectedProviders, () => this.triggerFoldingModelChanged(), this._foldingLimitReporter); } } return this.rangeProvider; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index e432965c8db..99ac22e555e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -695,8 +695,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- folding - $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, eventHandle: number | undefined): void { const provider = { + id: extensionId.value, provideFoldingRanges: (model, context, token) => { return this._proxy.$provideFoldingRanges(handle, model.uri, context, token); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8c2ab9090c4..baaa6b43d04 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -389,7 +389,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $emitInlayHintsEvent(eventHandle: number): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void; - $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, eventHandle: number | undefined): void; $emitFoldingRangeEvent(eventHandle: number, event?: any): void; $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index dad1a4ff5d1..02c1b73c78a 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2358,7 +2358,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF const eventHandle = typeof provider.onDidChangeFoldingRanges === 'function' ? this._nextHandle() : undefined; this._adapter.set(handle, new AdapterData(new FoldingProviderAdapter(this._documents, provider), extension)); - this._proxy.$registerFoldingRangeProvider(handle, this._transformDocumentSelector(selector), eventHandle); + this._proxy.$registerFoldingRangeProvider(handle, this._transformDocumentSelector(selector), extension.identifier, eventHandle); let result = this._createDisposable(handle); if (eventHandle !== undefined) { diff --git a/src/vs/workbench/contrib/folding/browser/folding.contribution.ts b/src/vs/workbench/contrib/folding/browser/folding.contribution.ts index 37a568faff4..62f86fa6022 100644 --- a/src/vs/workbench/contrib/folding/browser/folding.contribution.ts +++ b/src/vs/workbench/contrib/folding/browser/folding.contribution.ts @@ -12,7 +12,14 @@ import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/l import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { FoldingRangeProvider } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; const openSettingsCommand = 'workbench.action.openSettings'; const configureSettingsLabel = nls.localize('status.button.configure', "Configure"); @@ -93,3 +100,89 @@ Registry.as(WorkbenchExtensions.Workbench).regi FoldingLimitIndicatorContribution, LifecyclePhase.Restored ); + +class DefaultFoldingRangeProvider extends Disposable implements IWorkbenchContribution { + + static readonly configName = 'editor.defaultFoldingRangeProvider'; + + static extensionIds: (string | null)[] = []; + static extensionItemLabels: string[] = []; + static extensionDescriptions: string[] = []; + + constructor( + @IExtensionService private readonly _extensionService: IExtensionService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + this._store.add(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this)); + this._store.add(FoldingController.setFoldingRangeProviderSelector(this._selectFoldingRangeProvider.bind(this))); + + this._updateConfigValues(); + } + + private async _updateConfigValues(): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); + + DefaultFoldingRangeProvider.extensionIds.length = 0; + DefaultFoldingRangeProvider.extensionItemLabels.length = 0; + DefaultFoldingRangeProvider.extensionDescriptions.length = 0; + + DefaultFoldingRangeProvider.extensionIds.push(null); + DefaultFoldingRangeProvider.extensionItemLabels.push(nls.localize('null', 'All')); + DefaultFoldingRangeProvider.extensionDescriptions.push(nls.localize('nullFormatterDescription', "All active folding range providers")); + + const languageExtensions: IExtensionDescription[] = []; + const otherExtensions: IExtensionDescription[] = []; + + for (const extension of this._extensionService.extensions) { + if (extension.main || extension.browser) { + if (extension.categories?.find(cat => cat === 'Programming Languages')) { + languageExtensions.push(extension); + } else { + otherExtensions.push(extension); + } + } + } + + const sorter = (a: IExtensionDescription, b: IExtensionDescription) => a.name.localeCompare(b.name); + + for (const extension of languageExtensions.sort(sorter)) { + DefaultFoldingRangeProvider.extensionIds.push(extension.identifier.value); + DefaultFoldingRangeProvider.extensionItemLabels.push(extension.displayName ?? ''); + DefaultFoldingRangeProvider.extensionDescriptions.push(extension.description ?? ''); + } + for (const extension of otherExtensions.sort(sorter)) { + DefaultFoldingRangeProvider.extensionIds.push(extension.identifier.value); + DefaultFoldingRangeProvider.extensionItemLabels.push(extension.displayName ?? ''); + DefaultFoldingRangeProvider.extensionDescriptions.push(extension.description ?? ''); + } + } + + private _selectFoldingRangeProvider(providers: FoldingRangeProvider[], document: ITextModel): FoldingRangeProvider[] { + const value = this._configurationService.getValue(DefaultFoldingRangeProvider.configName, { overrideIdentifier: document.getLanguageId() }); + if (value) { + return providers.filter(p => p.id === value); + } + return providers; + } +} + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + ...editorConfigurationBaseNode, + properties: { + [DefaultFoldingRangeProvider.configName]: { + description: nls.localize('formatter.default', "Defines a default folding range provider which takes precedence over all other folding range provider. Must be the identifier of an extension contributing a folding range provider."), + type: ['string', 'null'], + default: null, + enum: DefaultFoldingRangeProvider.extensionIds, + enumItemLabels: DefaultFoldingRangeProvider.extensionItemLabels, + markdownEnumDescriptions: DefaultFoldingRangeProvider.extensionDescriptions + } + } +}); + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + DefaultFoldingRangeProvider, + LifecyclePhase.Restored +); +