generalize limit indicator and use for color decorators (#173730)

* generalize limit indicator and use for color decorators

* remove updateDebounceInfo max
This commit is contained in:
Martin Aeschlimann 2023-02-08 07:43:29 +01:00 committed by GitHub
parent cb671d149a
commit 911f119120
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 283 additions and 153 deletions

View file

@ -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"

View file

@ -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<string, string, any> = 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<BaseLanguageClient> {
@ -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<ColorInformation[] | null | undefined>(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
}
};

View file

@ -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);
}

View file

@ -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 [];

View file

@ -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<void>();
public readonly onDidChange: Event<void> = 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);

View file

@ -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<FoldingLimitInfo>();
public readonly onDidChangeFoldingLimit: Event<FoldingLimitInfo> = 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<void>();
public readonly onDidChange: Event<void> = 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<T> 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 <number>configurationService.getValue('editor.foldingMaximumRegions', { resource });
},
report: (info: FoldingLimitInfo) => { }
update: (computed: number, limited: number | false) => { }
};
const indentRangeProvider = new IndentRangeProvider(model, languageConfigurationService, foldingLimitReporter);

View file

@ -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 {

View file

@ -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;

View file

@ -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);

View file

@ -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++) {

View file

@ -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) {

View file

@ -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<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
FoldingLimitIndicatorContribution,
LifecyclePhase.Restored
);
class DefaultFoldingRangeProvider extends Disposable implements IWorkbenchContribution {
static readonly configName = 'editor.defaultFoldingRangeProvider';

View file

@ -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<void>;
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<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
LimitIndicatorContribution,
LifecyclePhase.Restored
);

View file

@ -20,7 +20,7 @@ type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean;
const foldingRangeLimit: FoldingLimitReporter = {
limit: 5000,
report: () => { }
update: () => { }
};
export class FoldingModel implements IDisposable {

View file

@ -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';