Skip to content

Commit

Permalink
Add support for maxItemsComputed
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-stripe committed Apr 20, 2021
1 parent 81800cd commit 7af2803
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The following settings are supported:
- `yaml.schemas`: Helps you associate schemas with files in a glob pattern
- `yaml.schemaStore.enable`: When set to true the YAML language server will pull in all available schemas from [JSON Schema Store](https://www.schemastore.org/json/)
- `yaml.customTags`: Array of custom tags that the parser will validate against. It has two ways to be used. Either an item in the array is a custom tag such as "!Ref" and it will automatically map !Ref to scalar or you can specify the type of the object !Ref should be e.g. "!Ref sequence". The type of object can be either scalar (for strings and booleans), sequence (for arrays), map (for objects).
- `yaml.maxItemsComputed`: The maximum number of outline symbols and folding regions computed (limited for performance reasons).
- `[yaml].editor.tabSize`: the number of spaces to use when autocompleting. Takes priority over editor.tabSize.
- `editor.tabSize`: the number of spaces to use when autocompleting. Default is 2.
- `http.proxy`: The URL of the proxy server that will be used when attempting to download a schema. If it is not set or it is undefined no proxy server will be used.
Expand Down
67 changes: 64 additions & 3 deletions src/languageserver/handlers/languageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import { isKubernetesAssociatedDocument } from '../../languageservice/parser/isK
import { LanguageService } from '../../languageservice/yamlLanguageService';
import { SettingsState } from '../../yamlSettings';
import { ValidationHandler } from './validationHandlers';
import { ResultLimitReachedNotification } from '../../requestTypes';
import * as path from 'path';

export class LanguageHandlers {
private languageService: LanguageService;
private yamlSettings: SettingsState;
private validationHandler: ValidationHandler;
private pendingLimitExceededWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: NodeJS.Timeout } };

constructor(
private readonly connection: Connection,
Expand All @@ -38,6 +41,7 @@ export class LanguageHandlers {
this.languageService = languageService;
this.yamlSettings = yamlSettings;
this.validationHandler = validationHandler;
this.pendingLimitExceededWarnings = {};
}

public registerHandlers(): void {
Expand All @@ -52,6 +56,9 @@ export class LanguageHandlers {
this.connection.onDocumentOnTypeFormatting((params) => this.formatOnTypeHandler(params));
this.connection.onCodeLens((params) => this.codeLensHandler(params));
this.connection.onCodeLensResolve((params) => this.codeLensResolveHandler(params));

this.yamlSettings.documents.onDidChangeContent((change) => this.cancelLimitExceededWarnings(change.document.uri));
this.yamlSettings.documents.onDidClose((event) => this.cancelLimitExceededWarnings(event.document.uri));
}

documentLinkHandler(params: DocumentLinkParams): Promise<DocumentLink[]> {
Expand All @@ -74,10 +81,18 @@ export class LanguageHandlers {
return;
}

const onResultLimitExceeded = this.onResultLimitExceeded(
document.uri,
this.yamlSettings.maxItemsComputed,
'document symbols'
);

const context = { resultLimit: this.yamlSettings.maxItemsComputed, onResultLimitExceeded };

if (this.yamlSettings.hierarchicalDocumentSymbolSupport) {
return this.languageService.findDocumentSymbols2(document);
return this.languageService.findDocumentSymbols2(document, context);
} else {
return this.languageService.findDocumentSymbols(document);
return this.languageService.findDocumentSymbols(document, context);
}
}

Expand Down Expand Up @@ -169,7 +184,17 @@ export class LanguageHandlers {
return;
}

return this.languageService.getFoldingRanges(textDocument, this.yamlSettings.capabilities.textDocument.foldingRange);
const capabilities = this.yamlSettings.capabilities.textDocument.foldingRange;
const rangeLimit = this.yamlSettings.maxItemsComputed || capabilities.rangeLimit;
const onRangeLimitExceeded = this.onResultLimitExceeded(textDocument.uri, rangeLimit, 'folding ranges');

const context = {
rangeLimit,
onRangeLimitExceeded,
lineFoldingOnly: capabilities.lineFoldingOnly,
};

return this.languageService.getFoldingRanges(textDocument, context);
}

codeActionHandler(params: CodeActionParams): CodeAction[] | undefined {
Expand All @@ -192,4 +217,40 @@ export class LanguageHandlers {
codeLensResolveHandler(param: CodeLens): Thenable<CodeLens> | CodeLens {
return this.languageService.resolveCodeLens(param);
}

// Adapted from:
// https://github.com/microsoft/vscode/blob/94c9ea46838a9a619aeafb7e8afd1170c967bb55/extensions/json-language-features/server/src/jsonServer.ts#L172
private cancelLimitExceededWarnings(uri: string): void {
const warning = this.pendingLimitExceededWarnings[uri];
if (warning && warning.timeout) {
clearTimeout(warning.timeout);
delete this.pendingLimitExceededWarnings[uri];
}
}

private onResultLimitExceeded(uri: string, resultLimit: number, name: string) {
return () => {
let warning = this.pendingLimitExceededWarnings[uri];
if (warning) {
if (!warning.timeout) {
// already shown
return;
}
warning.features[name] = name;
warning.timeout.refresh();
} else {
warning = { features: { [name]: name } };
warning.timeout = setTimeout(() => {
this.connection.sendNotification(
ResultLimitReachedNotification.type,
`${path.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(
' and '
)} have been limited to ${resultLimit} items.`
);
warning.timeout = undefined;
}, 2000);
this.pendingLimitExceededWarnings[uri] = warning;
}
};
}
}
2 changes: 2 additions & 0 deletions src/languageserver/handlers/settingsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export class SettingsHandler {
}
this.yamlSettings.customTags = settings.yaml.customTags ? settings.yaml.customTags : [];

this.yamlSettings.maxItemsComputed = Math.trunc(Math.max(0, Number(settings.yaml.maxItemsComputed))) || 5000;

if (settings.yaml.schemaStore) {
this.yamlSettings.schemaStoreEnabled = settings.yaml.schemaStore.enable;
}
Expand Down
15 changes: 11 additions & 4 deletions src/languageservice/services/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { SymbolInformation, DocumentSymbol } from 'vscode-languageserver-types';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONDocumentSymbols } from 'vscode-json-languageservice/lib/umd/services/jsonDocumentSymbols';
import { DocumentSymbolsContext } from 'vscode-json-languageservice/lib/umd/jsonLanguageTypes';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { yamlDocumentsCache } from '../parser/yaml-documents';

Expand All @@ -29,7 +30,10 @@ export class YAMLDocumentSymbols {
};
}

public findDocumentSymbols(document: TextDocument): SymbolInformation[] {
public findDocumentSymbols(
document: TextDocument,
context: DocumentSymbolsContext = { resultLimit: Number.MAX_VALUE }
): SymbolInformation[] {
const doc = yamlDocumentsCache.getYamlDocument(document);
if (!doc || doc['documents'].length === 0) {
return null;
Expand All @@ -38,14 +42,17 @@ export class YAMLDocumentSymbols {
let results = [];
for (const yamlDoc of doc['documents']) {
if (yamlDoc.root) {
results = results.concat(this.jsonDocumentSymbols.findDocumentSymbols(document, yamlDoc));
results = results.concat(this.jsonDocumentSymbols.findDocumentSymbols(document, yamlDoc, context));
}
}

return results;
}

public findHierarchicalDocumentSymbols(document: TextDocument): DocumentSymbol[] {
public findHierarchicalDocumentSymbols(
document: TextDocument,
context: DocumentSymbolsContext = { resultLimit: Number.MAX_VALUE }
): DocumentSymbol[] {
const doc = yamlDocumentsCache.getYamlDocument(document);
if (!doc || doc['documents'].length === 0) {
return null;
Expand All @@ -54,7 +61,7 @@ export class YAMLDocumentSymbols {
let results = [];
for (const yamlDoc of doc['documents']) {
if (yamlDoc.root) {
results = results.concat(this.jsonDocumentSymbols.findDocumentSymbols2(document, yamlDoc));
results = results.concat(this.jsonDocumentSymbols.findDocumentSymbols2(document, yamlDoc, context));
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/languageservice/services/yamlFolding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export function getFoldingRanges(document: TextDocument, context: FoldingRangesC
if (typeof rangeLimit !== 'number' || result.length <= rangeLimit) {
return result;
}
if (context && context.onRangeLimitExceeded) {
context.onRangeLimitExceeded(document.uri);
}

return result.slice(0, context.rangeLimit - 1);
}
Expand Down
6 changes: 3 additions & 3 deletions src/languageservice/yamlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { YAMLCompletion } from './services/yamlCompletion';
import { YAMLHover } from './services/yamlHover';
import { YAMLValidation } from './services/yamlValidation';
import { YAMLFormatter } from './services/yamlFormatter';
import { JSONDocument, DefinitionLink, TextDocument } from 'vscode-json-languageservice';
import { JSONDocument, DefinitionLink, TextDocument, DocumentSymbolsContext } from 'vscode-json-languageservice';
import { findLinks } from './services/yamlLinks';
import {
FoldingRange,
Expand Down Expand Up @@ -117,8 +117,8 @@ export interface LanguageService {
doComplete(document: TextDocument, position: Position, isKubernetes: boolean): Promise<CompletionList>;
doValidation(document: TextDocument, isKubernetes: boolean): Promise<Diagnostic[]>;
doHover(document: TextDocument, position: Position): Promise<Hover | null>;
findDocumentSymbols(document: TextDocument): SymbolInformation[];
findDocumentSymbols2(document: TextDocument): DocumentSymbol[];
findDocumentSymbols(document: TextDocument, context: DocumentSymbolsContext): SymbolInformation[];
findDocumentSymbols2(document: TextDocument, context: DocumentSymbolsContext): DocumentSymbol[];
findDefinition(document: TextDocument, position: Position, doc: JSONDocument): Promise<DefinitionLink[]>;
findLinks(document: TextDocument): Promise<DocumentLink[]>;
resetSchema(uri: string): boolean;
Expand Down
4 changes: 4 additions & 0 deletions src/languageservice/yamlTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export interface FoldingRangesContext {
* The maximal number of ranges returned.
*/
rangeLimit?: number;
/**
* Called when the result was cropped.
*/
onRangeLimitExceeded?: (uri: string) => void;
/**
* If set, the client signals that it only supports folding complete lines. If set, client will
* ignore specified `startCharacter` and `endCharacter` properties in a FoldingRange.
Expand Down
4 changes: 4 additions & 0 deletions src/requestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export namespace VSCodeContentRequestRegistration {
export const type: NotificationType<{}> = new NotificationType('yaml/registerContentRequest');
}

export namespace ResultLimitReachedNotification {
export const type: NotificationType<string> = new NotificationType('yaml/resultLimitReached');
}

export namespace VSCodeContentRequest {
export const type: RequestType<{}, {}, {}> = new RequestType('vscode/content');
}
Expand Down
2 changes: 2 additions & 0 deletions src/yamlSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Settings {
schemaStore: {
enable: boolean;
};
maxItemsComputed: number;
};
http: {
proxy: string;
Expand Down Expand Up @@ -54,6 +55,7 @@ export class SettingsState {
customTags = [];
schemaStoreEnabled = true;
indentation: string | undefined = undefined;
maxItemsComputed = 5000;

// File validation helpers
pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
Expand Down

0 comments on commit 7af2803

Please sign in to comment.