From 911f119120141940fcfc4d7899065ae85b72770a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Feb 2023 07:43:29 +0100 Subject: [PATCH] generalize limit indicator and use for color decorators (#173730) * generalize limit indicator and use for color decorators * remove updateDebounceInfo max --- build/lib/i18n.resources.json | 4 + .../client/src/jsonClient.ts | 40 +++-- .../client/src/languageStatus.ts | 13 +- .../server/src/jsonServer.ts | 9 + .../colorPicker/browser/colorDetector.ts | 34 +++- .../editor/contrib/folding/browser/folding.ts | 64 ++++--- .../folding/browser/indentRangeProvider.ts | 6 +- .../folding/browser/syntaxRangeProvider.ts | 4 +- .../test/browser/foldingRanges.test.ts | 2 +- .../folding/test/browser/indentFold.test.ts | 2 +- .../folding/test/browser/syntaxFold.test.ts | 2 +- .../folding/browser/folding.contribution.ts | 88 +--------- .../browser/limitIndicator.contribution.ts | 161 ++++++++++++++++++ .../browser/viewModel/foldingModel.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 5 +- 15 files changed, 283 insertions(+), 153 deletions(-) create mode 100644 src/vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 069a1d2515c..49087e64d02 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -114,6 +114,10 @@ "name": "vs/workbench/contrib/languageStatus", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/limitIndicator", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/keybindings", "project": "vscode-workbench" diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 650f20891a2..54a8ecc64ed 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -18,7 +18,7 @@ import { import { hash } from './utils/hash'; -import { createDocumentColorsLimitItem, createDocumentSymbolsLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; +import { createDocumentSymbolsLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; namespace VSCodeContentRequest { export const type: RequestType = new RequestType('vscode/content'); @@ -59,6 +59,8 @@ type Settings = { resultLimit?: number; jsonFoldingLimit?: number; jsoncFoldingLimit?: number; + jsonColorDecoratorLimit?: number; + jsoncColorDecoratorLimit?: number; }; http?: { proxy?: string; @@ -79,9 +81,12 @@ export namespace SettingIds { export const enableValidation = 'json.validate.enable'; export const enableSchemaDownload = 'json.schemaDownload.enable'; export const maxItemsComputed = 'json.maxItemsComputed'; + export const editorFoldingMaximumRegions = 'editor.foldingMaximumRegions'; + export const editorColorDecoratorsLimit = 'editor.colorDecoratorsLimit'; export const editorSection = 'editor'; export const foldingMaximumRegions = 'foldingMaximumRegions'; + export const colorDecoratorsLimit = 'colorDecoratorsLimit'; } export interface TelemetryReporter { @@ -109,6 +114,8 @@ export const languageServerDescription = l10n.t('JSON Language Server'); let resultLimit = 5000; let jsonFoldingLimit = 5000; let jsoncFoldingLimit = 5000; +let jsonColorDecoratorLimit = 5000; +let jsoncColorDecoratorLimit = 5000; export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { @@ -129,8 +136,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: let isClientReady = false; const documentSymbolsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentSymbolsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); - const documentColorsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentColorsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); - toDispose.push(documentSymbolsLimitStatusbarItem, documentColorsLimitStatusbarItem); + toDispose.push(documentSymbolsLimitStatusbarItem); toDispose.push(commands.registerCommand('json.clearCache', async () => { if (isClientReady && runtime.schemaRequests.clearCache) { @@ -225,20 +231,11 @@ export async function startClient(context: ExtensionContext, newLanguageClient: return r; }, provideDocumentColors(document: TextDocument, token: CancellationToken, next: ProvideDocumentColorsSignature) { - function checkLimit(r: ColorInformation[] | null | undefined): ColorInformation[] | null | undefined { - if (Array.isArray(r) && r.length > resultLimit) { - r.length = resultLimit; // truncate - documentColorsLimitStatusbarItem.update(document, resultLimit); - } else { - documentColorsLimitStatusbarItem.update(document, false); - } - return r; - } const r = next(document, token); if (isThenable(r)) { - return r.then(checkLimit); + return r; } - return checkLimit(r); + return r; }, provideDocumentSymbols(document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) { type T = SymbolInformation[] | DocumentSymbol[]; @@ -375,6 +372,8 @@ export async function startClient(context: ExtensionContext, newLanguageClient: updateFormatterRegistration(); } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { updateSchemaDownloadSetting(); + } else if (e.affectsConfiguration(SettingIds.editorFoldingMaximumRegions) || e.affectsConfiguration(SettingIds.editorColorDecoratorsLimit)) { + client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings() }); } })); @@ -470,8 +469,13 @@ function getSettings(): Settings { const normalizeLimit = (settingValue: any) => Math.trunc(Math.max(0, Number(settingValue))) || 5000; resultLimit = normalizeLimit(workspace.getConfiguration().get(SettingIds.maxItemsComputed)); - jsonFoldingLimit = normalizeLimit(workspace.getConfiguration(SettingIds.editorSection, { languageId: 'json' }).get(SettingIds.foldingMaximumRegions)); - jsoncFoldingLimit = normalizeLimit(workspace.getConfiguration(SettingIds.editorSection, { languageId: 'jsonc' }).get(SettingIds.foldingMaximumRegions)); + const editorJSONSettings = workspace.getConfiguration(SettingIds.editorSection, { languageId: 'json' }); + const editorJSONCSettings = workspace.getConfiguration(SettingIds.editorSection, { languageId: 'jsonc' }); + + jsonFoldingLimit = normalizeLimit(editorJSONSettings.get(SettingIds.foldingMaximumRegions)); + jsoncFoldingLimit = normalizeLimit(editorJSONCSettings.get(SettingIds.foldingMaximumRegions)); + jsonColorDecoratorLimit = normalizeLimit(editorJSONSettings.get(SettingIds.colorDecoratorsLimit)); + jsoncColorDecoratorLimit = normalizeLimit(editorJSONCSettings.get(SettingIds.colorDecoratorsLimit)); const schemas: JSONSchemaSettings[] = []; @@ -487,7 +491,9 @@ function getSettings(): Settings { schemas, resultLimit: resultLimit + 1, // ask for one more so we can detect if the limit has been exceeded jsonFoldingLimit: jsonFoldingLimit + 1, - jsoncFoldingLimit: jsoncFoldingLimit + 1 + jsoncFoldingLimit: jsoncFoldingLimit + 1, + jsonColorDecoratorLimit: jsonColorDecoratorLimit + 1, + jsoncColorDecoratorLimit: jsoncColorDecoratorLimit + 1 } }; diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts index 4bb409bda9e..f2c8b923c30 100644 --- a/extensions/json-language-features/client/src/languageStatus.ts +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -272,19 +272,10 @@ export function createDocumentSymbolsLimitItem(documentSelector: string[], setti const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector); statusItem.name = l10n.t('JSON Outline Status'); statusItem.severity = LanguageStatusSeverity.Warning; - statusItem.text = l10n.t('Outline Limited'); - statusItem.detail = l10n.t('only {0} document symbols shown', limit); + statusItem.text = l10n.t('Outline'); + statusItem.detail = l10n.t('only {0} document symbols shown for performance reasons', limit); statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; return Disposable.from(statusItem); } -export function createDocumentColorsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { - const statusItem = languages.createLanguageStatusItem('json.documentColorsStatus', documentSelector); - statusItem.name = l10n.t('JSON Color Symbol Status'); - statusItem.severity = LanguageStatusSeverity.Warning; - statusItem.text = l10n.t('Color Symbols Limited'); - statusItem.detail = l10n.t('only {0} color decorators shown', limit); - statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; - return Disposable.from(statusItem); -} diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index edbdf2c3af8..6a40666d18d 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -109,6 +109,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let resultLimit = Number.MAX_VALUE; let jsonFoldingRangeLimit = Number.MAX_VALUE; let jsoncFoldingRangeLimit = Number.MAX_VALUE; + let jsonColorDecoratorLimit = Number.MAX_VALUE; + let jsoncColorDecoratorLimit = Number.MAX_VALUE; + + let formatterMaxNumberOfEdits = Number.MAX_VALUE; let diagnosticsSupport: DiagnosticsSupport | undefined; @@ -190,6 +194,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) resultLimit?: number; jsonFoldingLimit?: number; jsoncFoldingLimit?: number; + jsonColorDecoratorLimit?: number; + jsoncColorDecoratorLimit?: number; }; http?: { proxy?: string; @@ -225,6 +231,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) resultLimit = sanitizeLimitSetting(settings.json?.resultLimit || Number.MAX_VALUE); jsonFoldingRangeLimit = sanitizeLimitSetting(settings.json?.jsonFoldingLimit || foldingRangeLimitDefault); jsoncFoldingRangeLimit = sanitizeLimitSetting(settings.json?.jsoncFoldingLimit || foldingRangeLimitDefault); + jsonColorDecoratorLimit = sanitizeLimitSetting(settings.json?.jsonColorDecoratorLimit || Number.MAX_VALUE); + jsoncColorDecoratorLimit = sanitizeLimitSetting(settings.json?.jsoncColorDecoratorLimit || Number.MAX_VALUE); // dynamically enable & disable the formatter if (dynamicFormatterRegistration) { @@ -422,6 +430,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) if (document) { const jsonDocument = getJSONDocument(document); + const resultLimit = document.languageId === 'jsonc' ? jsoncColorDecoratorLimit : jsonColorDecoratorLimit; return languageService.findDocumentColors(document, jsonDocument, { resultLimit }); } return []; diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 779bae9e416..2d190f6600f 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -6,6 +6,7 @@ import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; import { RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { noBreakWhitespace } from 'vs/base/common/strings'; @@ -46,6 +47,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { private readonly _ruleFactory = new DynamicCssRules(this._editor); + private readonly _decoratorLimitReporter = new DecoratorLimitReporter(); + constructor( private readonly _editor: ICodeEditor, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -97,8 +100,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { return this._editor.getOption(EditorOption.colorDecorators); } - private getDecoratorLimit(): number { - return this._editor.getOption(EditorOption.colorDecoratorsLimit); + public get limitReporter() { + return this._decoratorLimitReporter; } static get(editor: ICodeEditor): ColorDetector | null { @@ -191,7 +194,9 @@ export class ColorDetector extends Disposable implements IEditorContribution { const decorations: IModelDeltaDecoration[] = []; - for (let i = 0; i < colorData.length && decorations.length < this.getDecoratorLimit(); i++) { + const limit = this._editor.getOption(EditorOption.colorDecoratorsLimit); + + for (let i = 0; i < colorData.length && decorations.length < limit; i++) { const { red, green, blue, alpha } = colorData[i].colorInfo.color; const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha); const color = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`; @@ -220,6 +225,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { } }); } + const limited = limit < colorData.length ? limit : false; + this._decoratorLimitReporter.update(colorData.length, limited); this._colorDecoratorIds.set(decorations); } @@ -253,4 +260,25 @@ export class ColorDetector extends Disposable implements IEditorContribution { } } +export class DecoratorLimitReporter { + private _onDidChange = new Emitter(); + public readonly onDidChange: Event = this._onDidChange.event; + + private _computed: number = 0; + private _limited: number | false = false; + public get computed(): number { + return this._computed; + } + public get limited(): number | false { + return this._limited; + } + public update(computed: number, limited: number | false) { + if (computed !== this._computed || limited !== this._limited) { + this._computed = computed; + this._limited = limited; + this._onDidChange.fire(); + } + } +} + registerEditorContribution(ColorDetector.ID, ColorDetector, EditorContributionInstantiation.AfterFirstRender); diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index 2bd291740f0..baa610c8d31 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -59,12 +59,7 @@ interface FoldingStateMemento { export interface FoldingLimitReporter { readonly limit: number; - report(limitInfo: FoldingLimitInfo): void; -} - -export interface FoldingLimitInfo { - computed: number; - limited: number | false; + update(computed: number, limited: number | false): void; } export type FoldingRangeProviderSelector = (provider: FoldingRangeProvider[], document: ITextModel) => FoldingRangeProvider[] | undefined; @@ -96,7 +91,6 @@ export class FoldingController extends Disposable implements IEditorContribution private _restoringViewState: boolean; private _foldingImportsByDefault: boolean; private _currentModelHasFoldedImports: boolean; - private _foldingLimitReporter: FoldingLimitReporter; private readonly foldingDecorationProvider: FoldingDecorationProvider; @@ -116,13 +110,7 @@ export class FoldingController extends Disposable implements IEditorContribution private readonly localToDispose = this._register(new DisposableStore()); private mouseDownInfo: { lineNumber: number; iconClicked: boolean } | null; - private _onDidChangeFoldingLimit = new Emitter(); - public readonly onDidChangeFoldingLimit: Event = this._onDidChangeFoldingLimit.event; - - private _foldingLimitInfo: FoldingLimitInfo | undefined; - public get foldingLimitInfo() { - return this._foldingLimitInfo; - } + public readonly _foldingLimitReporter: RangesLimitReporter; constructor( editor: ICodeEditor, @@ -134,6 +122,9 @@ export class FoldingController extends Disposable implements IEditorContribution ) { super(); this.editor = editor; + + this._foldingLimitReporter = new RangesLimitReporter(editor); + const options = this.editor.getOptions(); this._isEnabled = options.get(EditorOption.folding); this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation'; @@ -141,17 +132,6 @@ export class FoldingController extends Disposable implements IEditorContribution this._restoringViewState = false; this._currentModelHasFoldedImports = false; this._foldingImportsByDefault = options.get(EditorOption.foldingImportsByDefault); - this._foldingLimitReporter = { - get limit() { - return editor.getOptions().get(EditorOption.foldingMaximumRegions); - }, - report: (info: FoldingLimitInfo) => { - if (!this._foldingLimitInfo || (info.limited !== this._foldingLimitInfo.limited)) { - this._foldingLimitInfo = info; - this._onDidChangeFoldingLimit.fire(info); - } - } - }; this.updateDebounceInfo = languageFeatureDebounceService.for(languageFeaturesService.foldingRangeProvider, 'Folding', { min: 200 }); this.foldingModel = null; @@ -200,6 +180,10 @@ export class FoldingController extends Disposable implements IEditorContribution this.onModelChanged(); } + public get limitReporter() { + return this._foldingLimitReporter; + } + /** * Store view state. */ @@ -529,6 +513,34 @@ export class FoldingController extends Disposable implements IEditorContribution } } +export class RangesLimitReporter implements FoldingLimitReporter { + constructor(private readonly editor: ICodeEditor) { + } + + public get limit() { + return this.editor.getOptions().get(EditorOption.foldingMaximumRegions); + } + + private _onDidChange = new Emitter(); + public readonly onDidChange: Event = this._onDidChange.event; + + private _computed: number = 0; + private _limited: number | false = false; + public get computed(): number { + return this._computed; + } + public get limited(): number | false { + return this._limited; + } + public update(computed: number, limited: number | false) { + if (computed !== this._computed || limited !== this._limited) { + this._computed = computed; + this._limited = limited; + this._onDidChange.fire(); + } + } +} + abstract class FoldingAction extends EditorAction { abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T, languageConfigurationService: ILanguageConfigurationService): void; @@ -1232,7 +1244,7 @@ CommandsRegistry.registerCommand('_executeFoldingRangeProvider', async function get limit() { return configurationService.getValue('editor.foldingMaximumRegions', { resource }); }, - report: (info: FoldingLimitInfo) => { } + update: (computed: number, limited: number | false) => { } }; const indentRangeProvider = new IndentRangeProvider(model, languageConfigurationService, foldingLimitReporter); diff --git a/src/vs/editor/contrib/folding/browser/indentRangeProvider.ts b/src/vs/editor/contrib/folding/browser/indentRangeProvider.ts index bb3662caafc..e3e83443ec1 100644 --- a/src/vs/editor/contrib/folding/browser/indentRangeProvider.ts +++ b/src/vs/editor/contrib/folding/browser/indentRangeProvider.ts @@ -66,7 +66,7 @@ class RangesCollector { public toIndentRanges(model: ITextModel) { const limit = this._foldingRangesLimit.limit; if (this._length <= limit) { - this._foldingRangesLimit.report({ limited: false, computed: this._length }); + this._foldingRangesLimit.update(this._length, false); // reverse and create arrays of the exact length const startIndexes = new Uint32Array(this._length); @@ -77,7 +77,7 @@ class RangesCollector { } return new FoldingRegions(startIndexes, endIndexes); } else { - this._foldingRangesLimit.report({ limited: limit, computed: this._length }); + this._foldingRangesLimit.update(this._length, limit); let entries = 0; let maxIndent = this._indentOccurrences.length; @@ -120,7 +120,7 @@ interface PreviousRegion { const foldingRangesLimitDefault: FoldingLimitReporter = { limit: MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT, - report: () => { } + update: () => { } }; export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit: FoldingLimitReporter = foldingRangesLimitDefault): FoldingRegions { diff --git a/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.ts b/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.ts index a8f91c9b14e..49e03d708fe 100644 --- a/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.ts +++ b/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.ts @@ -122,7 +122,7 @@ class RangesCollector { public toIndentRanges() { const limit = this._foldingRangesLimit.limit; if (this._length <= limit) { - this._foldingRangesLimit.report({ limited: false, computed: this._length }); + this._foldingRangesLimit.update(this._length, false); const startIndexes = new Uint32Array(this._length); const endIndexes = new Uint32Array(this._length); @@ -132,7 +132,7 @@ class RangesCollector { } return new FoldingRegions(startIndexes, endIndexes, this._types); } else { - this._foldingRangesLimit.report({ limited: limit, computed: this._length }); + this._foldingRangesLimit.update(this._length, limit); let entries = 0; let maxLevel = this._nestingLevelCounts.length; diff --git a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts index 734613f4d13..d8215e7aa41 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts @@ -42,7 +42,7 @@ suite('FoldingRanges', () => { lines.push('#endregion'); } const model = createTextModel(lines.join('\n')); - const actual = computeRanges(model, false, markers, { limit: MAX_FOLDING_REGIONS, report: () => { } }); + const actual = computeRanges(model, false, markers, { limit: MAX_FOLDING_REGIONS, update: () => { } }); assert.strictEqual(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { assert.strictEqual(actual.getStartLineNumber(i), i + 1, 'start' + i); diff --git a/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts b/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts index 3d37b2bde9f..3145d1ab5aa 100644 --- a/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts @@ -51,7 +51,7 @@ suite('Indentation Folding', () => { function assertLimit(maxEntries: number, expectedRanges: IndentRange[], message: string) { let reported: number | false = false; - const indentRanges = computeRanges(model, true, undefined, { limit: maxEntries, report: r => reported = r.limited }); + const indentRanges = computeRanges(model, true, undefined, { limit: maxEntries, update: (computed, limited) => reported = limited }); assert.ok(indentRanges.length <= maxEntries, 'max ' + message); const actual: IndentRange[] = []; for (let i = 0; i < indentRanges.length; i++) { diff --git a/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts b/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts index 3f5013ca608..72f6046db88 100644 --- a/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts @@ -76,7 +76,7 @@ suite('Syntax folding', () => { async function assertLimit(maxEntries: number, expectedRanges: IndentRange[], message: string) { let reported: number | false = false; - const foldingRangesLimit: FoldingLimitReporter = { limit: maxEntries, report: r => reported = r.limited }; + const foldingRangesLimit: FoldingLimitReporter = { limit: maxEntries, update: (computed, limited) => reported = limited }; const indentRanges = await new SyntaxRangeProvider(model, providers, () => { }, foldingRangesLimit, undefined).compute(CancellationToken.None); const actual: IndentRange[] = []; if (indentRanges) { diff --git a/src/vs/workbench/contrib/folding/browser/folding.contribution.ts b/src/vs/workbench/contrib/folding/browser/folding.contribution.ts index 26db1d87761..185b51f047d 100644 --- a/src/vs/workbench/contrib/folding/browser/folding.contribution.ts +++ b/src/vs/workbench/contrib/folding/browser/folding.contribution.ts @@ -3,12 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import Severity from 'vs/base/common/severity'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { FoldingController, FoldingLimitInfo } from 'vs/editor/contrib/folding/browser/folding'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; 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'; @@ -21,86 +17,6 @@ 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"); - -const foldingMaximumRegionsSettingsId = 'editor.foldingMaximumRegions'; - -export class FoldingLimitIndicatorContribution extends Disposable implements IWorkbenchContribution { - - constructor( - @IEditorService private readonly editorService: IEditorService, - @ILanguageStatusService private readonly languageStatusService: ILanguageStatusService - ) { - super(); - - let changeListener: IDisposable | undefined; - let control: any; - - const onActiveEditorChanged = () => { - const activeControl = editorService.activeTextEditorControl; - if (activeControl === control) { - return; - } - control = undefined; - if (changeListener) { - changeListener.dispose(); - changeListener = undefined; - } - const editor = getCodeEditor(activeControl); - if (editor) { - const controller = FoldingController.get(editor); - if (controller) { - const info = controller.foldingLimitInfo; - this.updateLimitInfo(info); - control = activeControl; - changeListener = controller.onDidChangeFoldingLimit(info => { - this.updateLimitInfo(info); - }); - } else { - this.updateLimitInfo(undefined); - } - } else { - this.updateLimitInfo(undefined); - } - }; - - this._register(this.editorService.onDidActiveEditorChange(onActiveEditorChanged)); - - onActiveEditorChanged(); - } - - private _limitStatusItem: IDisposable | undefined; - - private updateLimitInfo(info: FoldingLimitInfo | undefined) { - if (this._limitStatusItem) { - this._limitStatusItem.dispose(); - this._limitStatusItem = undefined; - } - if (info && info.limited !== false) { - const status: ILanguageStatus = { - id: 'foldingLimitInfo', - selector: '*', - name: nls.localize('foldingRangesStatusItem.name', 'Folding Status'), - severity: Severity.Warning, - label: nls.localize('status.limitedFoldingRanges.short', 'Folding Ranges Limited'), - detail: nls.localize('status.limitedFoldingRanges.details', 'only {0} folding ranges shown for performance reasons', info.limited), - command: { id: openSettingsCommand, arguments: [foldingMaximumRegionsSettingsId], title: configureSettingsLabel }, - accessibilityInfo: undefined, - source: nls.localize('foldingRangesStatusItem.source', 'Folding'), - busy: false - }; - this._limitStatusItem = this.languageStatusService.addStatus(status); - } - - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( - FoldingLimitIndicatorContribution, - LifecyclePhase.Restored -); - class DefaultFoldingRangeProvider extends Disposable implements IWorkbenchContribution { static readonly configName = 'editor.defaultFoldingRangeProvider'; diff --git a/src/vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution.ts b/src/vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution.ts new file mode 100644 index 00000000000..29acb09ab9d --- /dev/null +++ b/src/vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import Severity from 'vs/base/common/severity'; +import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import * as nls from 'vs/nls'; + +import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; +import { ColorDetector } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; + +const openSettingsCommand = 'workbench.action.openSettings'; +const configureSettingsLabel = nls.localize('status.button.configure', "Configure"); + +/** + * Uses that language status indicator to show information which language features have been limited for performance reasons. + * Currently this is used for folding ranges and for color decorators. + */ +export class LimitIndicatorContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @IEditorService editorService: IEditorService, + @ILanguageStatusService languageStatusService: ILanguageStatusService + ) { + super(); + + const accessors = [new ColorDecorationAccessor(), new FoldingRangeAccessor()]; + const statusEntries = accessors.map(indicator => new LanguageStatusEntry(languageStatusService, indicator)); + statusEntries.forEach(entry => this._register(entry)); + + let control: any; + + const onActiveEditorChanged = () => { + const activeControl = editorService.activeTextEditorControl; + if (activeControl === control) { + return; + } + control = activeControl; + const editor = getCodeEditor(activeControl); + + statusEntries.forEach(statusEntry => statusEntry.onActiveEditorChanged(editor)); + }; + this._register(editorService.onDidActiveEditorChange(onActiveEditorChanged)); + + onActiveEditorChanged(); + } + +} + + +export interface LimitInfo { + readonly onDidChange: Event; + + readonly computed: number; + readonly limited: number | false; +} + +interface LanguageFeatureAccessor { + readonly id: string; + readonly name: string; + readonly label: string; + readonly source: string; + readonly settingsId: string; + getLimitReporter(editor: ICodeEditor): LimitInfo | undefined; +} + +class ColorDecorationAccessor implements LanguageFeatureAccessor { + readonly id = 'decoratorsLimitInfo'; + readonly name = nls.localize('colorDecoratorsStatusItem.name', 'Color Decorator Status'); + readonly label = nls.localize('status.limitedColorDecorators.short', 'Color Decorators'); + readonly source = nls.localize('colorDecoratorsStatusItem.source', 'Color Decorators'); + readonly settingsId = 'editor.colorDecoratorsLimit'; + + getLimitReporter(editor: ICodeEditor): LimitInfo | undefined { + return ColorDetector.get(editor)?.limitReporter; + } +} + +class FoldingRangeAccessor implements LanguageFeatureAccessor { + readonly id = 'foldingLimitInfo'; + readonly name = nls.localize('foldingRangesStatusItem.name', 'Folding Status'); + readonly label = nls.localize('status.limitedFoldingRanges.short', 'Folding Ranges'); + readonly source = nls.localize('foldingRangesStatusItem.source', 'Folding'); + readonly settingsId = 'editor.foldingMaximumRegions'; + + getLimitReporter(editor: ICodeEditor): LimitInfo | undefined { + return FoldingController.get(editor)?.limitReporter; + } +} + +class LanguageStatusEntry { + + private _limitStatusItem: IDisposable | undefined; + private _indicatorChangeListener: IDisposable | undefined; + + constructor(private languageStatusService: ILanguageStatusService, private accessor: LanguageFeatureAccessor) { + } + + onActiveEditorChanged(editor: ICodeEditor | null): boolean { + if (this._indicatorChangeListener) { + this._indicatorChangeListener.dispose(); + this._indicatorChangeListener = undefined; + } + + let info: LimitInfo | undefined; + if (editor) { + info = this.accessor.getLimitReporter(editor); + } + this.updateStatusItem(info); + if (info) { + this._indicatorChangeListener = info.onDidChange(_ => { + this.updateStatusItem(info); + }); + return true; + } + return false; + } + + + private updateStatusItem(info: LimitInfo | undefined) { + if (this._limitStatusItem) { + this._limitStatusItem.dispose(); + this._limitStatusItem = undefined; + } + if (info && info.limited !== false) { + const status: ILanguageStatus = { + id: this.accessor.id, + selector: '*', + name: this.accessor.name, + severity: Severity.Warning, + label: this.accessor.label, + detail: nls.localize('status.limited.details', 'only {0} shown for performance reasons', info.limited), + command: { id: openSettingsCommand, arguments: [this.accessor.settingsId], title: configureSettingsLabel }, + accessibilityInfo: undefined, + source: this.accessor.source, + busy: false + }; + this._limitStatusItem = this.languageStatusService.addStatus(status); + } + } + + public dispose() { + this._limitStatusItem?.dispose; + this._limitStatusItem = undefined; + this._indicatorChangeListener?.dispose; + this._indicatorChangeListener = undefined; + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + LimitIndicatorContribution, + LifecyclePhase.Restored +); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts index e77934ef939..143d969f6ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts @@ -20,7 +20,7 @@ type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean; const foldingRangeLimit: FoldingLimitReporter = { limit: 5000, - report: () => { } + update: () => { } }; export class FoldingModel implements IDisposable { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index e0c8d69815c..20342cec9fb 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -268,9 +268,12 @@ import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; // Formatter Help import 'vs/workbench/contrib/format/browser/format.contribution'; -// Folding Limit Indicator +// Folding import 'vs/workbench/contrib/folding/browser/folding.contribution'; +// Limit Indicator +import 'vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution'; + // Inlay Hint Accessibility import 'vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty';