Skip to content

Add expandable hover#3311

Open
jakebailey wants to merge 35 commits intomainfrom
jabaile/expand-hover
Open

Add expandable hover#3311
jakebailey wants to merge 35 commits intomainfrom
jabaile/expand-hover

Conversation

@jakebailey
Copy link
Copy Markdown
Member

@jakebailey jakebailey commented Apr 1, 2026

Closes #2457

This is a proposed VS Code feature. To make this work, we add the new API ontop of the LSP, then disable the LSP client's hover provider, in favor of one with the feature.

@jakebailey jakebailey requested a review from gabritto April 1, 2026 20:23
@jakebailey jakebailey marked this pull request as ready for review April 1, 2026 22:29
Copilot AI review requested due to automatic review settings April 1, 2026 22:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an “expandable hover” feature end-to-end: a proposed VS Code hover API that can request increased “verbosity”, an LSP extension (verbosityLevel on HoverParams + canIncreaseVerbosity on Hover), server plumbing to honor it, and fourslash coverage/baselines for the new hover behavior.

Changes:

  • Extend LSP hover request/response shapes to support verbosity (verbosityLevel) and server-provided expandability (canIncreaseVerbosity).
  • Add a VS Code extension hover provider that uses the proposed hover verbosity API and sends verbosityLevel over LSP (disabling the languageclient’s built-in hover).
  • Add fourslash harness support + many new baselines/tests for hover verbosity scenarios, plus conversion-script support for verify.baselineQuickInfo({...}).

Reviewed changes

Copilot reviewed 91 out of 93 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/lsp/server.go Routes hover requests to verbose hover implementation when verbosityLevel is present.
internal/lsp/lsproto/lsp_generated.go Adds verbosityLevel to HoverParams, canIncreaseVerbosity to Hover, and a capabilities flag.
internal/lsp/lsproto/_generate/generate.mts Patches the LSP model so the generator emits the new hover fields/capabilities.
_extension/src/languageFeatures/hover.ts New VS Code hover provider that requests verbose hover via LSP and maps expand/decrease flags.
_extension/src/vscode.proposed.editorHoverVerbosityLevel.d.ts Declares the proposed VS Code API surface (VerboseHover, HoverContext, etc.).
_extension/src/client.ts Advertises hover verbosity capability + registers the custom hover provider; disables LSP hover via middleware.
_extension/package.json Enables the editorHoverVerbosityLevel proposal and bumps VS Code engine/types.
package-lock.json Updates VS Code typings version to match the engine bump.
internal/checker/printer.go Adds verbosity-aware type/signature/symbol-to-declaration stringification helpers.
internal/checker/nodebuilder.go Adds verbosity-aware nodebuilder entry points and symbol-declaration rendering helpers.
internal/checker/services.go Introduces Checker.IsLibType used to prevent expanding default-lib types.
internal/ls/completions.go Refactors completion item detail generation; threads hover quickinfo via the updated quickinfo helper signature.
internal/ls/string_completions.go Updates call into completion details helper after signature refactor.
internal/fourslash/fourslash.go Adds VerifyBaselineHoverWithVerbosity helper for producing multi-verbosity hover baselines.
internal/fourslash/baselineutil.go Uses stable sorting when inserting multiple tooltips at the same position.
internal/fourslash/_scripts/convertFourslash.mts Adds support for verify.baselineQuickInfo({...verbosity...}) conversion into Go tests.
internal/fourslash/_scripts/unparsedTests.txt Removes now-supported quickinfoVerbosity tests from the “unparsed” list.
internal/fourslash/_scripts/failingTests.txt Updates the list of failing tests (removals).
internal/fourslash/tests/quickinfoVerbosityNamespaceDefaultExport_test.go New manual fourslash test for namespace default export verbosity behavior.
internal/fourslash/tests/gen/quickinfoVerbosity1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosity2_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosity3_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityClass1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityClass2_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityConditionalType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityEnum_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityFunction_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityImport_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityIndexedAccessType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityIndexSignature_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityIndexType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityInterface1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityInterface2_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityIntersection1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityJs_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityLibType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityMappedType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityNamespace_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityNamespaceDefaultExport_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityNoErrorTruncation1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityObjectType1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityRecursiveType_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityServer_test.go Generated hover verbosity test (server mode).
internal/fourslash/tests/gen/quickinfoVerbosityToplevelTruncation1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityToplevelTruncation2_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityTruncation1_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityTruncation2_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityTuple_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityTypeof_test.go Generated hover verbosity test.
internal/fourslash/tests/gen/quickinfoVerbosityTypeParameter_test.go Generated hover verbosity test.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosity1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosity2.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityConditionalType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityFunction.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityIndexSignature.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityIndexType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityIndexedAccessType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityIntersection1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityLibType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityMappedType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityNamespaceDefaultExport.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityNoErrorTruncation1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityObjectType1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityRecursiveType.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityServer.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityTruncation1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityToplevelTruncation1.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityToplevelTruncation2.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityTypeof.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickinfoVerbosityTypeParameter.baseline New hover verbosity baseline.
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsTypeParameterInFunction.baseline Updates baseline output for type parameter/parameter quickinfo display parts.
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsTypeParameterInClass.baseline Updates baseline output for class type parameter/parameter quickinfo display parts.
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsTypeParameterInTypeAlias.baseline Updates baseline output for type parameter quickinfo display parts.
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsTypeParameterInFunctionLikeInTypeAlias.baseline Updates baseline output for type parameter quickinfo display parts.
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsEnum1.baseline Updates enum quickinfo baseline (e.g. const enum rendering).
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsEnum2.baseline Updates enum quickinfo baseline (e.g. const enum rendering).
testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsEnum3.baseline Updates enum quickinfo baseline (e.g. const enum rendering).
testdata/baselines/reference/submodule/compiler/privateFieldsInClassExpressionDeclaration.js Updates compiler baseline output for private fields in .d.ts emit.
testdata/baselines/reference/submodule/compiler/privateFieldsInClassExpressionDeclaration.js.diff Updates compiler baseline diff for the same private field emit change.

Comment on lines +1996 to +2026
const verbosityLevels: Record<string, number[]> = {};
for (const prop of verbosityArg.properties) {
if (!ts.isPropertyAssignment(prop)) {
return undefined;
}
let name: string;
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
name = prop.name.text;
}
else if (ts.isNumericLiteral(prop.name)) {
name = prop.name.text;
}
else {
return undefined;
}
if (ts.isArrayLiteralExpression(prop.initializer)) {
const levels: number[] = [];
for (const elem of prop.initializer.elements) {
if (!ts.isNumericLiteral(elem)) {
return undefined;
}
levels.push(Number(elem.text));
}
verbosityLevels[name] = levels;
}
else if (ts.isNumericLiteral(prop.initializer)) {
verbosityLevels[name] = [Number(prop.initializer.text)];
}
else {
return undefined;
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseBaselineQuickInfo is declared to return VerifyBaselineQuickInfoCmd[], but several branches return undefined (e.g. when encountering unsupported property shapes). With strict: true in this folder’s tsconfig, this won’t type-check and will break the fourslash conversion scripts. Replace these return undefined paths with throw new Error(...) (or change the function’s return type to include undefined and handle it at the call site).

Copilot uses AI. Check for mistakes.
Comment on lines 64 to 68
// ^
// | ----------------------------------------------------------------------
// | ```tsx
// | (parameter) a: U
// | (parameter) a: U extends string
// | ```
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hover text shows (parameter) a: U extends string, which is not valid TypeScript type syntax for a parameter (the parameter’s type is U; the extends string part is a constraint on the type parameter declaration). This looks like a regression in quickinfo rendering for constrained type parameters; the hover should keep the parameter type as U (while type parameter hovers can show the constraint).

Copilot uses AI. Check for mistakes.
Comment on lines 169 to 174
// ^
// | ----------------------------------------------------------------------
// | ```tsx
// | (parameter) a: T
// | (parameter) a: T extends c<string>
// | ```
// |
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The baseline now renders constructor/method parameters as T extends c<string> / U extends c<string>, which is not valid parameter type syntax (constraints belong on the type parameter declarations, not at use sites). This suggests the quickinfo printer is incorrectly formatting type parameter uses as declarations when constrained.

Copilot uses AI. Check for mistakes.
@jakebailey
Copy link
Copy Markdown
Member Author

One icky thing is that symbolTableToDeclarationStatements is basically what used to power JS declaration emit, but now does not; everything here seems to be requiredish but the unported code is all JS stuff. Makes me wonder if this feature just needs a rethink now that we aren't doing JS in the same way?

@jakebailey jakebailey requested a review from gabritto April 6, 2026 20:32
// At maxExpansionDepth <= 0, no expansion will occur so we return false to allow type-node reuse.
func (b *NodeBuilderImpl) canPossiblyExpandType(t *Type) bool {
if b.ctx.maxExpansionDepth < 0 {
if b.ctx.maxExpansionDepth <= 0 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change? Couldn't we still fall into the case where maxDepth is 0 and depth is 0 and canIncreaseExpansionDepth is false and so this would return true before?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the bit that makes it so that simply enabling the client capability / sending level 0 does not change the output formatting. But otherwise I'm not sure what you mean

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but IIRC if we have a request with level set to 0, and we make canPossiblyExpandType return false, we will do type node reuse and not compute canIncreaseVerbosityLevel.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, we have to still do a little work I guess so this is set. I guess I didn't notice because the boolean is hidden if false, but obviously fourslash will keep querying with higher levels. Probably I should assert if we attempt to go higher but the previous response said no... maybe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Prettified type display in hover output

3 participants