Search code examples
c#vsixlanguageservice

Popup (hover) with Markdown from a Language Server Protocol (LSP)


I'm building a language server in C# (with VS2022) that implements the Language Server Protocol (LSP) 17.2.8 (https://www.nuget.org/packages/Microsoft.VisualStudio.LanguageServer.Protocol.Extensions)

I'm new to the Language server protocol, and there might be a simple solution to my problem. I created markdown content for a Hover popup, but the content is displayed as plain text.

This is the OnHover,

     [JsonRpcMethod(Methods.TextDocumentHoverName)]
     public Hover OnHover(JToken arg)
     {
         LogInfo($"OnHover: Received: {arg}");
         var parameter = arg.ToObject<TextDocumentPositionParams>();

         [SNIP details]

         var hoverContent = new SumType<string, MarkedString>[]{
             new SumType<string, MarkedString>(new MarkedString
             {
                 Language = MarkupKind.PlainText.ToString(),
                 Value = full_Descr + "\n",
             }),
             new SumType<string, MarkedString>(new MarkedString
             {
                 Language = MarkupKind.Markdown.ToString(),
                 Value = "```text\n" + performanceStr + "\n```",
             })
         };

         var result = new Hover()
         {
             Contents = hoverContent
         };

         LogInfo($"OnHover: Sent: {JToken.FromObject(result)}");
         return result;
     }

The language server sends the following message to my VisualStudio extension (vsix):

{
  "contents": [
    {
      "language": "PlainText",
      "value": "VPCONFLICTQ : [NONE,AVX512_CD] Detect Conflicts Within a Vector of Packed Dword/Qword Values into Dense Memory/ Register\n"
    },
    {
      "language": "Markdown",
      "value": "```text\n                                      µOps   µOps     µOps                                              \nArchitecture  Instruction             Fused  Unfused  Port                Latency  Throughput           \nSkylakeX      VPCONFLICTQ x,x         3      3        p01 p5              4        2                    \nSkylakeX      VPCONFLICTQ y,y         15     15       p01 p5              13       7                    \nSkylakeX      VPCONFLICTQ z,z         22     22       p0 p5               17       12                   \n```"
    }
  ]
}

When the language server is initialized, it sends (to my VS-extension) a JSON with capabilities (removed all other capabilities).

{
  "capabilities": {
    "hoverProvider": {}
  }
}

Question: Is there something else that I need to configure to get a popup with Markdown layout? Is Markdown not supported from the Visual studio side? Do I need HoverClientCapabilities to configure stuff?

The de facto reference implementation can be found in the VSSDK-Extensibility-Samples (https://github.com/microsoft/VSSDK-Extensibility-Samples/tree/master/LanguageServerProtocol), but it does not have a markdown popup. Even a link to working C# code would be appreciated.

Edit: see here for what I've tried.


Solution

  • From what I understand searching around, markdown doesn't work yet in VS 2022 (current version 17.7.4). Or at least Roslyn and other Microsoft products apparently use an undocumented Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalHover, which has an additional RawContent field that tells Visual Studio about the classifications etc. See examples below.

    Examples:

    • Roslyn

      return new VSInternalHover
      {
          Range = ProtocolConversions.TextSpanToRange(info.Span, text),
          Contents = new MarkupContent
          {
              Kind = MarkupKind.Markdown,
              Value = GetMarkdownString(descriptionBuilder)
          },
          RawContent = new ClassifiedTextElement(descriptionBuilder.Select(tp => new ClassifiedTextRun(tp.Tag.ToClassificationTypeName(), tp.Text)))
      };
      
    • Razor

    Also notice that there is a Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalClientCapabilities referenced in Roslyn.

    According to this comment, there is an open issue to support markdown in their internal issue tracker.


    EDIT: Digging through a decompilation of the Microsoft.VisualStudio.LanguageServer.Client.Implementation.dll, specifically Microsoft.VisualStudio.LanguageServer.Client.HoverSource.GetQuickInfoItemAsync(), Visual Studio checks the hover response for the internal type IHoverContent, then for VSInternalHover. If one of them is given, their RawContent is directly forwarded to the QuickInfoItem. A MarkedString or an array thereof are also forwarded. A string and the Value of MarkupContent apparently are not forwarded directly, but only content within markdown triple ticks ``` (for whatever reason).

    In any case, according to the documentation of QuickInfoItem, the object it contains is forwarded to IToolTipPresenter which should end up using IViewElementFactoryService, which says:

    The editor supports ClassifiedTextElements, ContainerElement, ImageElements, and Object on all platforms. Text and image elements are converted to colorized text and images respectively and other objects are displayed as the String returned by ToString() unless an extender exports a IViewElementFactory for that type. On Windows only, ITextBuffer, ITextView, and UIElement are also directly supported.

    So, Roslyn etc. give a ClassifiedTextElements directly, which can be displayed by QuickInfoItem in Visual Studio directly. As far as I can see, nothing in VS implements IViewElementFactory for MarkedString yet. So it ends up using its ToString() method.


    Conclusion: Markdown is not yet supported. The only workaround to get formatted hovers is to use the undocumented VSInternalHover.RawContent and set it to a ClassifiedTextElement (like Roslyn is doing).

    Since it is undocumented, this will most likely break in a future VS version. But hopefully markdown exists by then.