mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
[json] use language indicator for folding limit warnings (#153104)
* reduce number of json notifications * use language indicator for folding limit warnings. For #142496 * add comment for resultLimit + 1
This commit is contained in:
parent
bdfb4d0765
commit
b3cc787d78
|
@ -9,19 +9,19 @@ const localize = nls.loadMessageBundle();
|
|||
export type JSONLanguageStatus = { schemas: string[] };
|
||||
|
||||
import {
|
||||
workspace, window, languages, commands, ExtensionContext, extensions, Uri,
|
||||
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken,
|
||||
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString,
|
||||
workspace, window, languages, commands, ExtensionContext, extensions, Uri, ColorInformation,
|
||||
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange,
|
||||
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation
|
||||
} from 'vscode';
|
||||
import {
|
||||
LanguageClientOptions, RequestType, NotificationType,
|
||||
DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams,
|
||||
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient
|
||||
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient, ProvideFoldingRangeSignature, ProvideDocumentSymbolsSignature, ProvideDocumentColorsSignature
|
||||
} from 'vscode-languageclient';
|
||||
|
||||
|
||||
import { hash } from './utils/hash';
|
||||
import { createLanguageStatusItem } from './languageStatus';
|
||||
import { createDocumentColorsLimitItem, createDocumentSymbolsLimitItem, createFoldingRangeLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus';
|
||||
|
||||
namespace VSCodeContentRequest {
|
||||
export const type: RequestType<string, string, any> = new RequestType('vscode/content');
|
||||
|
@ -53,10 +53,6 @@ namespace SchemaAssociationNotification {
|
|||
export const type: NotificationType<ISchemaAssociations | ISchemaAssociation[]> = new NotificationType('json/schemaAssociations');
|
||||
}
|
||||
|
||||
namespace ResultLimitReachedNotification {
|
||||
export const type: NotificationType<string> = new NotificationType('json/resultLimitReached');
|
||||
}
|
||||
|
||||
type Settings = {
|
||||
json?: {
|
||||
schemas?: JSONSchemaSettings[];
|
||||
|
@ -76,17 +72,13 @@ export type JSONSchemaSettings = {
|
|||
schema?: any;
|
||||
};
|
||||
|
||||
namespace SettingIds {
|
||||
export namespace SettingIds {
|
||||
export const enableFormatter = 'json.format.enable';
|
||||
export const enableValidation = 'json.validate.enable';
|
||||
export const enableSchemaDownload = 'json.schemaDownload.enable';
|
||||
export const maxItemsComputed = 'json.maxItemsComputed';
|
||||
}
|
||||
|
||||
namespace StorageIds {
|
||||
export const maxItemsExceededInformation = 'json.maxItemsExceededInformation';
|
||||
}
|
||||
|
||||
export interface TelemetryReporter {
|
||||
sendTelemetryEvent(eventName: string, properties?: {
|
||||
[key: string]: string;
|
||||
|
@ -109,6 +101,8 @@ export interface SchemaRequestService {
|
|||
|
||||
export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server');
|
||||
|
||||
let resultLimit = 5000;
|
||||
|
||||
export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise<BaseLanguageClient> {
|
||||
|
||||
const toDispose = context.subscriptions;
|
||||
|
@ -127,6 +121,11 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
|
||||
let isClientReady = false;
|
||||
|
||||
const foldingRangeLimitStatusBarItem = createLimitStatusItem((limit: number) => createFoldingRangeLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
const documentSymbolsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentSymbolsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
const documentColorsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentColorsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
toDispose.push(foldingRangeLimitStatusBarItem, documentSymbolsLimitStatusbarItem, documentColorsLimitStatusbarItem);
|
||||
|
||||
toDispose.push(commands.registerCommand('json.clearCache', async () => {
|
||||
if (isClientReady && runtime.schemaRequests.clearCache) {
|
||||
const cachedSchemas = await runtime.schemaRequests.clearCache();
|
||||
|
@ -211,6 +210,60 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
return r.then(updateHover);
|
||||
}
|
||||
return updateHover(r);
|
||||
},
|
||||
provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken, next: ProvideFoldingRangeSignature) {
|
||||
function checkLimit(r: FoldingRange[] | null | undefined): FoldingRange[] | null | undefined {
|
||||
if (Array.isArray(r) && r.length > resultLimit) {
|
||||
r.length = resultLimit; // truncate
|
||||
foldingRangeLimitStatusBarItem.update(document, resultLimit);
|
||||
} else {
|
||||
foldingRangeLimitStatusBarItem.update(document, false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const r = next(document, context, token);
|
||||
if (isThenable<FoldingRange[] | null | undefined>(r)) {
|
||||
return r.then(checkLimit);
|
||||
}
|
||||
return checkLimit(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 checkLimit(r);
|
||||
},
|
||||
provideDocumentSymbols(document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) {
|
||||
type T = SymbolInformation[] | DocumentSymbol[];
|
||||
function countDocumentSymbols(symbols: DocumentSymbol[]): number {
|
||||
return symbols.reduce((previousValue, s) => previousValue + 1 + countDocumentSymbols(s.children), 0);
|
||||
}
|
||||
function isDocumentSymbol(r: T): r is DocumentSymbol[] {
|
||||
return r[0] instanceof DocumentSymbol;
|
||||
}
|
||||
function checkLimit(r: T | null | undefined): T | null | undefined {
|
||||
if (Array.isArray(r) && (isDocumentSymbol(r) ? countDocumentSymbols(r) : r.length) > resultLimit) {
|
||||
documentSymbolsLimitStatusbarItem.update(document, resultLimit);
|
||||
} else {
|
||||
documentSymbolsLimitStatusbarItem.update(document, false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const r = next(document, token);
|
||||
if (isThenable<T | undefined | null>(r)) {
|
||||
return r.then(checkLimit);
|
||||
}
|
||||
return checkLimit(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -328,21 +381,6 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
}
|
||||
}));
|
||||
|
||||
client.onNotification(ResultLimitReachedNotification.type, async message => {
|
||||
const shouldPrompt = context.globalState.get<boolean>(StorageIds.maxItemsExceededInformation) !== false;
|
||||
if (shouldPrompt) {
|
||||
const ok = localize('ok', "OK");
|
||||
const openSettings = localize('goToSetting', 'Open Settings');
|
||||
const neverAgain = localize('yes never again', "Don't Show Again");
|
||||
const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain);
|
||||
if (pick === neverAgain) {
|
||||
await context.globalState.update(StorageIds.maxItemsExceededInformation, false);
|
||||
} else if (pick === openSettings) {
|
||||
await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri)));
|
||||
|
||||
function updateFormatterRegistration() {
|
||||
|
@ -432,7 +470,7 @@ function getSettings(): Settings {
|
|||
const configuration = workspace.getConfiguration();
|
||||
const httpSettings = workspace.getConfiguration('http');
|
||||
|
||||
const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
|
||||
resultLimit = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
|
||||
|
||||
const settings: Settings = {
|
||||
http: {
|
||||
|
@ -443,7 +481,7 @@ function getSettings(): Settings {
|
|||
validate: { enable: configuration.get(SettingIds.enableValidation) },
|
||||
format: { enable: configuration.get(SettingIds.enableFormatter) },
|
||||
schemas: [],
|
||||
resultLimit
|
||||
resultLimit: resultLimit + 1 // ask for one more so we can detect if the limit has been exceeded
|
||||
}
|
||||
};
|
||||
const schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null);
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, ThemeIcon } from 'vscode';
|
||||
import {
|
||||
window, languages, Uri, Disposable, commands, QuickPickItem,
|
||||
extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind,
|
||||
ThemeIcon, TextDocument, LanguageStatusSeverity
|
||||
} from 'vscode';
|
||||
import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
|
@ -187,13 +191,13 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
|||
statusItem.detail = undefined;
|
||||
if (schemas.length === 0) {
|
||||
statusItem.text = localize('status.noSchema.short', "No Schema Validation");
|
||||
statusItem.detail = localize('status.noSchema', 'No JSON schema configured.');
|
||||
statusItem.detail = localize('status.noSchema', 'no JSON schema configured');
|
||||
} else if (schemas.length === 1) {
|
||||
statusItem.text = localize('status.withSchema.short', "Schema Validated");
|
||||
statusItem.detail = localize('status.singleSchema', 'JSON schema configured.');
|
||||
statusItem.detail = localize('status.singleSchema', 'JSON schema configured');
|
||||
} else {
|
||||
statusItem.text = localize('status.withSchemas.short', "Schema Validated");
|
||||
statusItem.detail = localize('status.multipleSchema', 'Multiple JSON schemas configured.');
|
||||
statusItem.detail = localize('status.multipleSchema', 'multiple JSON schemas configured');
|
||||
}
|
||||
statusItem.command = {
|
||||
command: '_json.showAssociatedSchemaList',
|
||||
|
@ -217,3 +221,85 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
|||
return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);
|
||||
}
|
||||
|
||||
export function createLimitStatusItem(newItem: (limit: number) => Disposable) {
|
||||
let statusItem: Disposable | undefined;
|
||||
const activeLimits: Map<TextDocument, number> = new Map();
|
||||
|
||||
const toDispose: Disposable[] = [];
|
||||
toDispose.push(window.onDidChangeActiveTextEditor(textEditor => {
|
||||
statusItem?.dispose();
|
||||
statusItem = undefined;
|
||||
const doc = textEditor?.document;
|
||||
if (doc) {
|
||||
const limit = activeLimits.get(doc);
|
||||
if (limit !== undefined) {
|
||||
statusItem = newItem(limit);
|
||||
}
|
||||
}
|
||||
}));
|
||||
toDispose.push(workspace.onDidCloseTextDocument(document => {
|
||||
activeLimits.delete(document);
|
||||
}));
|
||||
|
||||
function update(document: TextDocument, limitApplied: number | false) {
|
||||
if (limitApplied === false) {
|
||||
activeLimits.delete(document);
|
||||
if (statusItem && document === window.activeTextEditor?.document) {
|
||||
statusItem.dispose();
|
||||
statusItem = undefined;
|
||||
}
|
||||
} else {
|
||||
activeLimits.set(document, limitApplied);
|
||||
if (document === window.activeTextEditor?.document) {
|
||||
if (!statusItem || limitApplied !== activeLimits.get(document)) {
|
||||
statusItem?.dispose();
|
||||
statusItem = newItem(limitApplied);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
update,
|
||||
dispose() {
|
||||
statusItem?.dispose();
|
||||
toDispose.forEach(d => d.dispose());
|
||||
toDispose.length = 0;
|
||||
statusItem = undefined;
|
||||
activeLimits.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const openSettingsCommand = 'workbench.action.openSettings';
|
||||
const configureSettingsLabel = localize('status.button.configure', "Configure");
|
||||
|
||||
export function createFoldingRangeLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.foldingRangesStatus', documentSelector);
|
||||
statusItem.name = localize('foldingRangesStatusItem.name', "JSON Folding Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedFoldingRanges.short', "Folding Ranges Limited");
|
||||
statusItem.detail = localize('status.limitedFoldingRanges.details', 'only {0} folding ranges shown', limit);
|
||||
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
|
||||
return Disposable.from(statusItem);
|
||||
}
|
||||
|
||||
export function createDocumentSymbolsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector);
|
||||
statusItem.name = localize('documentSymbolsStatusItem.name', "JSON Outline Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedDocumentSymbols.short', "Outline Limited");
|
||||
statusItem.detail = localize('status.limitedDocumentSymbols.details', 'only {0} document symbols shown', 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 = localize('documentColorsStatusItem.name', "JSON Color Symbol Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedDocumentColors.short', "Color Symbols Limited");
|
||||
statusItem.detail = localize('status.limitedDocumentColors.details', 'only {0} color decorators shown', limit);
|
||||
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
|
||||
return Disposable.from(statusItem);
|
||||
}
|
||||
|
||||
|
|
|
@ -188,12 +188,6 @@ Notification:
|
|||
### Item Limit
|
||||
|
||||
If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed.
|
||||
When the limit is reached, a notification `json/resultLimitReached` is sent that can be shown that can be shown to the user.
|
||||
|
||||
Notification:
|
||||
- method: 'json/resultLimitReached'
|
||||
- params: a human readable string to show to the user.
|
||||
|
||||
|
||||
## Try
|
||||
|
||||
|
|
|
@ -31,10 +31,6 @@ namespace SchemaContentChangeNotification {
|
|||
export const type: NotificationType<string | string[]> = new NotificationType('json/schemaContent');
|
||||
}
|
||||
|
||||
namespace ResultLimitReachedNotification {
|
||||
export const type: NotificationType<string> = new NotificationType('json/resultLimitReached');
|
||||
}
|
||||
|
||||
namespace ForceValidateRequest {
|
||||
export const type: RequestType<string, Diagnostic[], any> = new RequestType('json/validate');
|
||||
}
|
||||
|
@ -204,44 +200,6 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
|||
}
|
||||
|
||||
|
||||
const limitExceededWarnings = function () {
|
||||
const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: Disposable } } = {};
|
||||
|
||||
const showLimitedNotification = (uri: string, resultLimit: number) => {
|
||||
const warning = pendingWarnings[uri];
|
||||
connection.sendNotification(ResultLimitReachedNotification.type, `${Utils.basename(URI.parse(uri))}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`);
|
||||
warning.timeout = undefined;
|
||||
};
|
||||
|
||||
return {
|
||||
cancel(uri: string) {
|
||||
const warning = pendingWarnings[uri];
|
||||
if (warning && warning.timeout) {
|
||||
warning.timeout.dispose();
|
||||
delete pendingWarnings[uri];
|
||||
}
|
||||
},
|
||||
|
||||
onResultLimitExceeded(uri: string, resultLimit: number, name: string) {
|
||||
return () => {
|
||||
let warning = pendingWarnings[uri];
|
||||
if (warning) {
|
||||
if (!warning.timeout) {
|
||||
// already shown
|
||||
return;
|
||||
}
|
||||
warning.features[name] = name;
|
||||
warning.timeout.dispose();
|
||||
warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000);
|
||||
} else {
|
||||
warning = { features: { [name]: name } };
|
||||
warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000);
|
||||
pendingWarnings[uri] = warning;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
||||
let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined;
|
||||
let schemaAssociations: ISchemaAssociations | SchemaConfiguration[] | undefined = undefined;
|
||||
|
@ -417,11 +375,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
|||
const document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
if (document) {
|
||||
const jsonDocument = getJSONDocument(document);
|
||||
const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document symbols');
|
||||
if (hierarchicalDocumentSymbolSupport) {
|
||||
return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit });
|
||||
} else {
|
||||
return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit });
|
||||
}
|
||||
}
|
||||
return [];
|
||||
|
@ -453,9 +410,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
|||
return runSafeAsync(runtime, async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors');
|
||||
|
||||
const jsonDocument = getJSONDocument(document);
|
||||
return languageService.findDocumentColors(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentColors(document, jsonDocument, { resultLimit });
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
|
||||
|
@ -476,8 +433,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
|||
return runSafe(runtime, () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const onRangeLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges');
|
||||
return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit, onRangeLimitExceeded });
|
||||
return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit });
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token);
|
||||
|
|
Loading…
Reference in a new issue