Add classified text output for VS Find All References#4030
Conversation
|
|
||
| meaning := getIntersectingMeaningFromDeclarations(originalNode, symbol, ast.SemanticMeaningAll) | ||
|
|
||
| info := getQuickInfoAndDeclarationAtLocation(c, symbol, originalNode, nil /*vsCapability */, vsCapability, meaning) |
There was a problem hiding this comment.
This is the equivalent of getSymbolDisplayPartsDocumentationAndSymbolKind() in Strada
| if !vsCapability { | ||
| dpw.Write(c.TypeToStringEx(t, enclosing, flags, vc)) | ||
| return | ||
| } | ||
| emitContext := printer.NewEmitContext() | ||
| idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol) | ||
| nb := checker.NewNodeBuilderEx(c, emitContext, idToSymbol) | ||
| combinedFlags := nodebuilder.Flags(flags&checker.TypeFormatFlagsNodeBuilderFlagsMask) | classifiedNodeBuilderFlags | ||
| typeNode := nb.TypeToTypeNode(t, enclosing, combinedFlags, nodebuilder.InternalFlagsNone, nil) | ||
| if typeNode == nil { | ||
| dpw.Write(c.TypeToStringEx(t, enclosing, flags, vc)) | ||
| return | ||
| } | ||
| p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) | ||
| p.IdToSymbol = idToSymbol | ||
| tempDpw := newDisplayPartsWriter(true) | ||
| p.Write(typeNode, sourceFile, tempDpw, nil) | ||
| dpw.WriteFrom(tempDpw) |
There was a problem hiding this comment.
When VS capability is enabled, we can't use TypeToStringEx (which produces a flat string). Instead, we go through NodeBuilder → Printer → displayPartsWriter so each token (keywords, type names, punctuation) gets its own classification for FAR coloring.
| @@ -139,6 +139,7 @@ type Printer struct { | |||
| makeFileLevelOptimisticUniqueName func(string) string | |||
| commentStateArena core.Arena[commentState] | |||
| sourceMapStateArena core.Arena[sourceMapState] | |||
| IdToSymbol map[*ast.IdentifierNode]*ast.Symbol | |||
There was a problem hiding this comment.
The NodeBuilder tracks which symbol each synthesized identifier maps to, but the Go port doesn't attach symbols directly to AST nodes like the old TS emitter did. IdToSymbol bridges that gap so the printer can call WriteSymbol (instead of plain Write) for identifiers, enabling proper classification (e.g., "class name", "interface name"). This field is nil by default and only set in VS-capability code paths, so it has no impact on normal printer usage.
| "ClassificationTypeName": "method name", | ||
| "Text": "foo" | ||
| "Text": "foo", | ||
| "MarkerTagType": null, | ||
| "Style": 0, | ||
| "_vs_type": "ClassifiedTextRun" |
There was a problem hiding this comment.
The sig help baselines changed because ClassifiedTextRun struct now always serializes _vs_type, MarkerTagType, and Style fields (previously omitted with omitzero). The sig help logic itself didn't change — it's just the JSON shape of the same struct that FAR also uses. Without this, VS wouldn't be able to reliably deserialize the runs in the FAR response.
…o navyasingh/FARbug2799994
Problem:
Visual Studio uses a custom LSP extension textDocument/_vs_references (instead of the standard textDocument/references) that returns richer data — specifically, grouped reference items with ClassifiedTextRun[] for definition text. This is what powers the colored syntax-highlighted definitions in VS's FAR panel (e.g., showing class Foo with "class" in blue and "Foo" in teal).
The standard LSP textDocument/references only returns Location[] (file + range) — no definition text, no classification. VS needs the extra metadata to render its FAR panel properly, which is why _vs_referencesProvider and _vs_supportsVisualStudioExtensions exist as VS-specific capability extensions
When using Find All References in VS with Corsa, the FAR panel was rendering definition text as flat uncolored "text" instead of properly syntax-highlighted runs. For example,
constructor globalClass(): globalClasswould appear as a single gray string instead of showing constructor as a keyword (blue),globalClassas a class name (teal), and () as punctuation.Additionally, 8 FAR test cases were panicking due to nil pointer dereferences when processing triple-slash references and module references in the VS code path.
How it worked in Strada
In the Strada architecture, the tsserver returned pre-classified DisplayParts[] for each definition — an array where every part had a kind (e.g., "keyword", "className", "punctuation", "parameterName"). The C# client-side FindAllReferencesService then mapped each display part kind to a Roslyn ClassificationTypeName via TSSymbolDisplayPart.GetClassificationName():
// TSSymbolDisplayPart.cs
This produced richly-colored ClassifiedTextRun[] elements that VS rendered with proper syntax highlighting in the FAR panel.
How it works now (Corsa)
In the native LSP server, there is no tsserver returning pre-classified parts. Instead, we have the checker's TypeToStringEx/SignatureToStringEx/SymbolToStringEx which produce flat strings.
The fix uses the same internal machinery that the checker uses — the node builder + printer pipeline — but routes output through the displayPartsWriter which classifies each token by type:
The printer was previously unable to classify identifiers because the Go port doesn't attach symbols directly to AST nodes (unlike the old TypeScript emitter). We bridge this gap by passing the node builder's idToSymbol map to the printer, enabling it to call WriteSymbol(text, symbol) for identifiers — which then uses classificationForSymbol to determine the correct classification from the symbol's flags.
This produces output like:
This display writer was created and similar changes were added for SignatureHelp as well in #3821. These are the same fourslash FAR scenarios that already exist in the non-VS test suite, re-run with the VS capability flag enabled. This lets us baseline the ClassifiedTextRun[] output and verify that definitions render with proper per-token classification (keywords, type names, punctuation, etc.) rather than flat text.