Skip to content

Commit 0ee67b5

Browse files
authored
Add configurable maximum hover length (#61662)
1 parent 8c62e08 commit 0ee67b5

22 files changed

+11670
-66
lines changed

src/compiler/checker.ts

+45-25
Large diffs are not rendered by default.

src/compiler/types.ts

+36-4
Original file line numberDiff line numberDiff line change
@@ -5143,7 +5143,16 @@ export interface TypeChecker {
51435143
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
51445144
/** Note that the resulting nodes cannot be checked. */
51455145
typeParameterToDeclaration(parameter: TypeParameter, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): TypeParameterDeclaration | undefined;
5146-
/** @internal */ typeParameterToDeclaration(parameter: TypeParameter, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker, verbosityLevel?: number, out?: WriterContextOut): TypeParameterDeclaration | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
5146+
/** @internal */ typeParameterToDeclaration(
5147+
parameter: TypeParameter,
5148+
enclosingDeclaration: Node | undefined,
5149+
flags: NodeBuilderFlags | undefined,
5150+
internalFlags?: InternalNodeBuilderFlags,
5151+
tracker?: SymbolTracker,
5152+
maximumLength?: number,
5153+
verbosityLevel?: number,
5154+
out?: WriterContextOut, // eslint-disable-line @typescript-eslint/unified-signatures
5155+
): TypeParameterDeclaration | undefined;
51475156

51485157
getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
51495158
getSymbolAtLocation(node: Node): Symbol | undefined;
@@ -5175,8 +5184,25 @@ export interface TypeChecker {
51755184
symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): string;
51765185
typePredicateToString(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string;
51775186

5178-
/** @internal */ writeSignature(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind, writer?: EmitTextWriter, verbosityLevel?: number, out?: WriterContextOut): string;
5179-
/** @internal */ writeType(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer?: EmitTextWriter, verbosityLevel?: number, out?: WriterContextOut): string;
5187+
/** @internal */ writeSignature(
5188+
signature: Signature,
5189+
enclosingDeclaration?: Node,
5190+
flags?: TypeFormatFlags,
5191+
kind?: SignatureKind,
5192+
writer?: EmitTextWriter,
5193+
maximumLength?: number,
5194+
verbosityLevel?: number,
5195+
out?: WriterContextOut,
5196+
): string;
5197+
/** @internal */ writeType(
5198+
type: Type,
5199+
enclosingDeclaration?: Node,
5200+
flags?: TypeFormatFlags,
5201+
writer?: EmitTextWriter,
5202+
maximumLength?: number,
5203+
verbosityLevel?: number,
5204+
out?: WriterContextOut,
5205+
): string;
51805206
/** @internal */ writeSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags, writer?: EmitTextWriter): string;
51815207
/** @internal */ writeTypePredicate(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer?: EmitTextWriter): string;
51825208

@@ -5896,7 +5922,7 @@ export interface EmitResolver {
58965922
isImportRequiredByAugmentation(decl: ImportDeclaration): boolean;
58975923
isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean;
58985924
createLateBoundIndexSignatures(cls: ClassLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): (IndexSignatureDeclaration | PropertyDeclaration)[] | undefined;
5899-
symbolToDeclarations(symbol: Symbol, meaning: SymbolFlags, flags: NodeBuilderFlags, verbosityLevel?: number, out?: WriterContextOut): Declaration[];
5925+
symbolToDeclarations(symbol: Symbol, meaning: SymbolFlags, flags: NodeBuilderFlags, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut): Declaration[];
59005926
}
59015927

59025928
// dprint-ignore
@@ -10517,6 +10543,12 @@ export interface UserPreferences {
1051710543
readonly displayPartsForJSDoc?: boolean;
1051810544
readonly generateReturnInDocTemplate?: boolean;
1051910545
readonly disableLineTextInReferences?: boolean;
10546+
/**
10547+
* A positive integer indicating the maximum length of a hover text before it is truncated.
10548+
*
10549+
* Default: `500`
10550+
*/
10551+
readonly maximumHoverLength?: number;
1052010552
}
1052110553

1052210554
export type OrganizeImportsTypeOrder = "last" | "inline" | "first";

src/compiler/utilities.ts

+2
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,8 @@ export const externalHelpersModuleNameText = "tslib";
614614
export const defaultMaximumTruncationLength = 160;
615615
/** @internal */
616616
export const noTruncationMaximumTruncationLength = 1_000_000;
617+
/** @internal */
618+
export const defaultHoverMaximumTruncationLength = 500;
617619

618620
/** @internal */
619621
export function getDeclarationOfKind<T extends Declaration>(symbol: Symbol, kind: T["kind"]): T | undefined {

src/harness/client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export class SessionClient implements LanguageService {
254254
return { line, character: offset };
255255
}
256256

257-
getQuickInfoAtPosition(fileName: string, position: number, verbosityLevel?: number | undefined): QuickInfo {
257+
getQuickInfoAtPosition(fileName: string, position: number, maximumLength?: number, verbosityLevel?: number | undefined): QuickInfo {
258258
const args = { ...this.createFileLocationRequestArgs(fileName, position), verbosityLevel };
259259

260260
const request = this.processRequest<protocol.QuickInfoRequest>(protocol.CommandTypes.Quickinfo, args);

src/harness/fourslashImpl.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2455,11 +2455,16 @@ export class TestState {
24552455
return result;
24562456
}
24572457

2458-
public baselineQuickInfo(verbosityLevels?: VerbosityLevels): void {
2458+
public baselineQuickInfo(verbosityLevels?: VerbosityLevels, maximumLength?: number): void {
24592459
const result = ts.arrayFrom(this.testData.markerPositions.entries(), ([name, marker]) => {
24602460
const verbosityLevel = toArray(verbosityLevels?.[name]);
24612461
const items = verbosityLevel.map(verbosityLevel => {
2462-
const item: ts.QuickInfo & { verbosityLevel?: number; } | undefined = this.languageService.getQuickInfoAtPosition(marker.fileName, marker.position, verbosityLevel);
2462+
const item: ts.QuickInfo & { verbosityLevel?: number; } | undefined = this.languageService.getQuickInfoAtPosition(
2463+
marker.fileName,
2464+
marker.position,
2465+
maximumLength,
2466+
verbosityLevel,
2467+
);
24632468
if (item) item.verbosityLevel = verbosityLevel;
24642469
return {
24652470
marker: { ...marker, name },

src/harness/fourslashInterfaceImpl.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@ export class Verify extends VerifyNegatable {
449449
this.state.baselineGetEmitOutput();
450450
}
451451

452-
public baselineQuickInfo(verbosityLevels?: FourSlash.VerbosityLevels): void {
453-
this.state.baselineQuickInfo(verbosityLevels);
452+
public baselineQuickInfo(verbosityLevels?: FourSlash.VerbosityLevels, maximumLength?: number): void {
453+
this.state.baselineQuickInfo(verbosityLevels, maximumLength);
454454
}
455455

456456
public baselineSignatureHelp(): void {

src/server/session.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -2395,12 +2395,18 @@ export class Session<TMessage = string> implements EventSender {
23952395
private getQuickInfoWorker(args: protocol.QuickInfoRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo | undefined {
23962396
const { file, project } = this.getFileAndProject(args);
23972397
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2398-
const quickInfo = project.getLanguageService().getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo), args.verbosityLevel);
2398+
const userPreferences = this.getPreferences(file);
2399+
const quickInfo = project.getLanguageService().getQuickInfoAtPosition(
2400+
file,
2401+
this.getPosition(args, scriptInfo),
2402+
userPreferences.maximumHoverLength,
2403+
args.verbosityLevel,
2404+
);
23992405
if (!quickInfo) {
24002406
return undefined;
24012407
}
24022408

2403-
const useDisplayParts = !!this.getPreferences(file).displayPartsForJSDoc;
2409+
const useDisplayParts = !!userPreferences.displayPartsForJSDoc;
24042410
if (simplifiedResult) {
24052411
const displayString = displayPartsToString(quickInfo.displayParts);
24062412
return {

src/services/services.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
Debug,
5050
Declaration,
5151
deduplicate,
52+
defaultHoverMaximumTruncationLength,
5253
DefinitionInfo,
5354
DefinitionInfoAndBoundSpan,
5455
Diagnostic,
@@ -2274,7 +2275,7 @@ export function createLanguageService(
22742275
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences);
22752276
}
22762277

2277-
function getQuickInfoAtPosition(fileName: string, position: number, verbosityLevel?: number): QuickInfo | undefined {
2278+
function getQuickInfoAtPosition(fileName: string, position: number, maximumLength?: number, verbosityLevel?: number): QuickInfo | undefined {
22782279
synchronizeHostData();
22792280

22802281
const sourceFile = getValidSourceFile(fileName);
@@ -2310,6 +2311,7 @@ export function createLanguageService(
23102311
nodeForQuickInfo,
23112312
/*semanticMeaning*/ undefined,
23122313
/*alias*/ undefined,
2314+
maximumLength ?? defaultHoverMaximumTruncationLength,
23132315
verbosityLevel,
23142316
),
23152317
);

src/services/symbolDisplay.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
266266
type: Type | undefined,
267267
semanticMeaning: SemanticMeaning,
268268
alias?: Symbol,
269+
maximumLength?: number,
269270
verbosityLevel?: number,
270271
): SymbolDisplayPartsDocumentationAndSymbolKind {
271272
const displayParts: SymbolDisplayPart[] = [];
@@ -495,6 +496,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
495496
location.parent && isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol),
496497
enclosingDeclaration,
497498
TypeFormatFlags.InTypeAlias,
499+
maximumLength,
498500
verbosityLevel,
499501
typeWriterOut,
500502
),
@@ -602,6 +604,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
602604
type,
603605
semanticMeaning,
604606
shouldUseAliasName ? symbol : resolvedSymbol,
607+
maximumLength,
605608
verbosityLevel,
606609
);
607610
displayParts.push(...resolvedInfo.displayParts);
@@ -700,11 +703,12 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
700703
symbolDisplayNodeBuilderFlags,
701704
/*internalFlags*/ undefined,
702705
/*tracker*/ undefined,
706+
maximumLength,
703707
verbosityLevel,
704708
typeWriterOut,
705709
)!;
706710
getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer);
707-
});
711+
}, maximumLength);
708712
addRange(displayParts, typeParameterParts);
709713
}
710714
else {
@@ -715,6 +719,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
715719
type,
716720
enclosingDeclaration,
717721
/*flags*/ undefined,
722+
maximumLength,
718723
verbosityLevel,
719724
typeWriterOut,
720725
),
@@ -889,6 +894,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
889894
symbol,
890895
symbolMeaning,
891896
TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
897+
maximumLength,
892898
verbosityLevel !== undefined ? verbosityLevel - 1 : undefined,
893899
typeWriterOut,
894900
);
@@ -898,7 +904,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
898904
if (i > 0) writer.writeLine();
899905
printer.writeNode(EmitHint.Unspecified, node, sourceFile, writer);
900906
});
901-
});
907+
}, maximumLength);
902908
addRange(displayParts, expandedDisplayParts);
903909
symbolWasExpanded = true;
904910
return true;
@@ -973,7 +979,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
973979
}
974980

975981
function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) {
976-
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature, verbosityLevel, typeWriterOut));
982+
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature, maximumLength, verbosityLevel, typeWriterOut));
977983
if (allSignatures.length > 1) {
978984
displayParts.push(spacePart());
979985
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
@@ -1011,9 +1017,21 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(
10111017
location: Node,
10121018
semanticMeaning: SemanticMeaning = getMeaningFromLocation(location),
10131019
alias?: Symbol,
1020+
maximumLength?: number,
10141021
verbosityLevel?: number,
10151022
): SymbolDisplayPartsDocumentationAndSymbolKind {
1016-
return getSymbolDisplayPartsDocumentationAndSymbolKindWorker(typeChecker, symbol, sourceFile, enclosingDeclaration, location, /*type*/ undefined, semanticMeaning, alias, verbosityLevel);
1023+
return getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
1024+
typeChecker,
1025+
symbol,
1026+
sourceFile,
1027+
enclosingDeclaration,
1028+
location,
1029+
/*type*/ undefined,
1030+
semanticMeaning,
1031+
alias,
1032+
maximumLength,
1033+
verbosityLevel,
1034+
);
10171035
}
10181036

10191037
function isLocalVariableOrFunction(symbol: Symbol) {

src/services/types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -581,10 +581,11 @@ export interface LanguageService {
581581
*
582582
* @param fileName The path to the file
583583
* @param position A zero-based index of the character where you want the quick info
584+
* @param maximumLength Maximum length of a quickinfo text before it is truncated.
584585
*/
585-
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined;
586+
getQuickInfoAtPosition(fileName: string, position: number, maximumLength?: number): QuickInfo | undefined;
586587
/** @internal */
587-
getQuickInfoAtPosition(fileName: string, position: number, verbosityLevel: number | undefined): QuickInfo | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
588+
getQuickInfoAtPosition(fileName: string, position: number, maximumLength: number | undefined, verbosityLevel: number | undefined): QuickInfo | undefined;
588589

589590
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined;
590591

src/services/utilities.ts

+35-10
Original file line numberDiff line numberDiff line change
@@ -2761,9 +2761,17 @@ export function isFirstDeclarationOfSymbolParameter(symbol: Symbol): boolean {
27612761
return !!findAncestor(declaration, n => isParameter(n) ? true : isBindingElement(n) || isObjectBindingPattern(n) || isArrayBindingPattern(n) ? false : "quit");
27622762
}
27632763

2764-
const displayPartWriter = getDisplayPartWriter();
2765-
function getDisplayPartWriter(): DisplayPartsSymbolWriter {
2766-
const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios
2764+
const displayPartWriterCache = new Map<number, DisplayPartsSymbolWriter>();
2765+
function getDisplayPartWriter(maximumLength: number | undefined): DisplayPartsSymbolWriter {
2766+
maximumLength = maximumLength || defaultMaximumTruncationLength;
2767+
if (!displayPartWriterCache.has(maximumLength)) {
2768+
displayPartWriterCache.set(maximumLength, getDisplayPartWriterWorker(maximumLength));
2769+
}
2770+
return displayPartWriterCache.get(maximumLength)!;
2771+
}
2772+
2773+
function getDisplayPartWriterWorker(maximumLength: number): DisplayPartsSymbolWriter {
2774+
const absoluteMaximumLength = maximumLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios
27672775
let displayParts: SymbolDisplayPart[];
27682776
let lineStart: boolean;
27692777
let indent: number;
@@ -3036,7 +3044,8 @@ export function lineBreakPart(): SymbolDisplayPart {
30363044
}
30373045

30383046
/** @internal */
3039-
export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] {
3047+
export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void, maximumLength?: number): SymbolDisplayPart[] {
3048+
const displayPartWriter = getDisplayPartWriter(maximumLength);
30403049
try {
30413050
writeDisplayParts(displayPartWriter);
30423051
return displayPartWriter.displayParts();
@@ -3047,10 +3056,18 @@ export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbol
30473056
}
30483057

30493058
/** @internal */
3050-
export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None, verbosityLevel?: number, out?: WriterContextOut): SymbolDisplayPart[] {
3059+
export function typeToDisplayParts(
3060+
typechecker: TypeChecker,
3061+
type: Type,
3062+
enclosingDeclaration?: Node,
3063+
flags: TypeFormatFlags = TypeFormatFlags.None,
3064+
maximumLength?: number,
3065+
verbosityLevel?: number,
3066+
out?: WriterContextOut,
3067+
): SymbolDisplayPart[] {
30513068
return mapToDisplayParts(writer => {
3052-
typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer, verbosityLevel, out);
3053-
});
3069+
typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer, maximumLength, verbosityLevel, out);
3070+
}, maximumLength);
30543071
}
30553072

30563073
/** @internal */
@@ -3061,11 +3078,19 @@ export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, e
30613078
}
30623079

30633080
/** @internal */
3064-
export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None, verbosityLevel?: number, out?: WriterContextOut): SymbolDisplayPart[] {
3081+
export function signatureToDisplayParts(
3082+
typechecker: TypeChecker,
3083+
signature: Signature,
3084+
enclosingDeclaration?: Node,
3085+
flags: TypeFormatFlags = TypeFormatFlags.None,
3086+
maximumLength?: number,
3087+
verbosityLevel?: number,
3088+
out?: WriterContextOut,
3089+
): SymbolDisplayPart[] {
30653090
flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers;
30663091
return mapToDisplayParts(writer => {
3067-
typechecker.writeSignature(signature, enclosingDeclaration, flags, /*kind*/ undefined, writer, verbosityLevel, out);
3068-
});
3092+
typechecker.writeSignature(signature, enclosingDeclaration, flags, /*kind*/ undefined, writer, maximumLength, verbosityLevel, out);
3093+
}, maximumLength);
30693094
}
30703095

30713096
/** @internal */

0 commit comments

Comments
 (0)