[clangd] Represent Hover result using FormattedString
authorIlya Biryukov <ibiryukov@google.com>
Wed, 29 May 2019 10:01:00 +0000 (10:01 +0000)
committerIlya Biryukov <ibiryukov@google.com>
Wed, 29 May 2019 10:01:00 +0000 (10:01 +0000)
Reviewers: sammccall, kadircet

Reviewed By: kadircet

Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D61601

llvm-svn: 361940

clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/FormattedString.cpp
clang-tools-extra/clangd/FormattedString.h
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/XRefs.cpp
clang-tools-extra/clangd/XRefs.h
clang-tools-extra/clangd/unittests/XRefsTests.cpp

index c599e6c..5332629 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "ClangdLSPServer.h"
 #include "Diagnostics.h"
+#include "FormattedString.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "Trace.h"
@@ -358,6 +359,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
   SupportsHierarchicalDocumentSymbol =
       Params.capabilities.HierarchicalDocumentSymbol;
   SupportFileStatus = Params.initializationOptions.FileStatus;
+  HoverContentFormat = Params.capabilities.HoverContentFormat;
   llvm::json::Object Result{
       {{"capabilities",
         llvm::json::Object{
@@ -843,17 +845,27 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
                               Callback<llvm::Optional<Hover>> Reply) {
   Server->findHover(Params.textDocument.uri.file(), Params.position,
                     Bind(
-                        [](decltype(Reply) Reply,
-                           llvm::Expected<llvm::Optional<HoverInfo>> HIorErr) {
-                          if (!HIorErr)
-                            return Reply(HIorErr.takeError());
-                          const auto &HI = HIorErr.get();
-                          if (!HI)
+                        [this](decltype(Reply) Reply,
+                               llvm::Expected<llvm::Optional<HoverInfo>> H) {
+                          if (!H)
+                            return Reply(H.takeError());
+                          if (!*H)
                             return Reply(llvm::None);
-                          Hover H;
-                          H.range = HI->SymRange;
-                          H.contents = HI->render();
-                          return Reply(H);
+
+                          Hover R;
+                          R.contents.kind = HoverContentFormat;
+                          R.range = (*H)->SymRange;
+                          switch (HoverContentFormat) {
+                          case MarkupKind::PlainText:
+                            R.contents.value =
+                                (*H)->present().renderAsPlainText();
+                            return Reply(std::move(R));
+                          case MarkupKind::Markdown:
+                            R.contents.value =
+                                (*H)->present().renderAsMarkdown();
+                            return Reply(std::move(R));
+                          };
+                          llvm_unreachable("unhandled MarkupKind");
                         },
                         std::move(Reply)));
 }
index f0b10a2..238e9af 100644 (file)
@@ -154,7 +154,10 @@ private:
   bool SupportsHierarchicalDocumentSymbol = false;
   /// Whether the client supports showing file status.
   bool SupportFileStatus = false;
-  // Store of the current versions of the open documents.
+  /// Which kind of markup should we use in textDocument/hover responses.
+  MarkupKind HoverContentFormat = MarkupKind::PlainText;
+
+  /// Store of the current versions of the open documents.
   DraftStore DraftMgr;
 
   // The CDB is created by the "initialize" LSP method.
index 9989f61..ca1500b 100644 (file)
 #include "ClangdUnit.h"
 #include "CodeComplete.h"
 #include "FindSymbols.h"
+#include "FormattedString.h"
 #include "Headers.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "TUScheduler.h"
 #include "Trace.h"
+#include "XRefs.h"
 #include "index/CanonicalIncludes.h"
 #include "index/FileIndex.h"
 #include "index/Merge.h"
@@ -462,7 +464,7 @@ void ClangdServer::findDocumentHighlights(
 
 void ClangdServer::findHover(PathRef File, Position Pos,
                              Callback<llvm::Optional<HoverInfo>> CB) {
-  auto Action = [Pos](Callback<llvm::Optional<HoverInfo>> CB, Path File,
+  auto Action = [Pos](decltype(CB) CB, Path File,
                       llvm::Expected<InputsAndAST> InpAST) {
     if (!InpAST)
       return CB(InpAST.takeError());
index 62c0c9a..c0389f0 100644 (file)
@@ -14,6 +14,7 @@
 #include "ClangdUnit.h"
 #include "CodeComplete.h"
 #include "FSProvider.h"
+#include "FormattedString.h"
 #include "Function.h"
 #include "GlobalCompilationDatabase.h"
 #include "Protocol.h"
index 3ae1a3c..3be179b 100644 (file)
@@ -9,6 +9,7 @@
 #include "clang/Basic/CharInfo.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/FormatVariadic.h"
 #include <cstddef>
 #include <string>
 
@@ -169,5 +170,27 @@ std::string FormattedString::renderAsPlainText() const {
     R.pop_back();
   return R;
 }
+
+std::string FormattedString::renderForTests() const {
+  std::string R;
+  for (const auto &C : Chunks) {
+    switch (C.Kind) {
+    case ChunkKind::PlainText:
+      R += "text[" + C.Contents + "]";
+      break;
+    case ChunkKind::InlineCodeBlock:
+      R += "code[" + C.Contents + "]";
+      break;
+    case ChunkKind::CodeBlock:
+      if (!R.empty())
+        R += "\n";
+      R += llvm::formatv("codeblock({0}) [\n{1}\n]\n", C.Language, C.Contents);
+      break;
+    }
+  }
+  while (!R.empty() && isWhitespace(R.back()))
+    R.pop_back();
+  return R;
+}
 } // namespace clangd
 } // namespace clang
index f20c19a..10313a4 100644 (file)
@@ -35,6 +35,7 @@ public:
 
   std::string renderAsMarkdown() const;
   std::string renderAsPlainText() const;
+  std::string renderForTests() const;
 
 private:
   enum class ChunkKind {
index 04c2663..51316fe 100644 (file)
@@ -303,6 +303,17 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) {
               DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
         R.HierarchicalDocumentSymbol = *HierarchicalSupport;
     }
+    if (auto *Hover = TextDocument->getObject("hover")) {
+      if (auto *ContentFormat = Hover->getArray("contentFormat")) {
+        for (const auto &Format : *ContentFormat) {
+          MarkupKind K = MarkupKind::PlainText;
+          if (fromJSON(Format, K)) {
+            R.HoverContentFormat = K;
+            break;
+          }
+        }
+      }
+    }
   }
   if (auto *Workspace = O->getObject("workspace")) {
     if (auto *Symbol = Workspace->getObject("symbol")) {
@@ -684,6 +695,27 @@ static llvm::StringRef toTextKind(MarkupKind Kind) {
   llvm_unreachable("Invalid MarkupKind");
 }
 
+bool fromJSON(const llvm::json::Value &V, MarkupKind &K) {
+  auto Str = V.getAsString();
+  if (!Str) {
+    elog("Failed to parse markup kind: expected a string");
+    return false;
+  }
+  if (*Str == "plaintext")
+    K = MarkupKind::PlainText;
+  else if (*Str == "markdown")
+    K = MarkupKind::Markdown;
+  else {
+    elog("Unknown markup kind: {0}", *Str);
+    return false;
+  }
+  return true;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind K) {
+  return OS << toTextKind(K);
+}
+
 llvm::json::Value toJSON(const MarkupContent &MC) {
   if (MC.value.empty())
     return nullptr;
index 957e2f3..21bac8a 100644 (file)
@@ -353,6 +353,15 @@ llvm::json::Value toJSON(const OffsetEncoding &);
 bool fromJSON(const llvm::json::Value &, OffsetEncoding &);
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, OffsetEncoding);
 
+// Describes the content type that a client supports in various result literals
+// like `Hover`, `ParameterInfo` or `CompletionItem`.
+enum class MarkupKind {
+  PlainText,
+  Markdown,
+};
+bool fromJSON(const llvm::json::Value &, MarkupKind &);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind);
+
 // This struct doesn't mirror LSP!
 // The protocol defines deeply nested structures for client capabilities.
 // Instead of mapping them all, this just parses out the bits we care about.
@@ -391,6 +400,9 @@ struct ClientCapabilities {
 
   /// Supported encodings for LSP character offsets. (clangd extension).
   llvm::Optional<std::vector<OffsetEncoding>> offsetEncoding;
+
+  /// The content format that should be used for Hover requests.
+  MarkupKind HoverContentFormat = MarkupKind::PlainText;
 };
 bool fromJSON(const llvm::json::Value &, ClientCapabilities &);
 
@@ -861,11 +873,6 @@ struct CompletionParams : TextDocumentPositionParams {
 };
 bool fromJSON(const llvm::json::Value &, CompletionParams &);
 
-enum class MarkupKind {
-  PlainText,
-  Markdown,
-};
-
 struct MarkupContent {
   MarkupKind kind = MarkupKind::PlainText;
   std::string value;
index 4b9b953..818dbc5 100644 (file)
@@ -9,6 +9,7 @@
 #include "AST.h"
 #include "CodeCompletionStrings.h"
 #include "FindSymbols.h"
+#include "FormattedString.h"
 #include "Logger.h"
 #include "Protocol.h"
 #include "SourceCode.h"
@@ -1155,32 +1156,26 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
   return Result;
 }
 
-MarkupContent HoverInfo::render() const {
-  MarkupContent Content;
-  Content.kind = MarkupKind::PlainText;
-  std::vector<std::string> Output;
-
+FormattedString HoverInfo::present() const {
+  FormattedString Output;
   if (NamespaceScope) {
-    llvm::raw_string_ostream Out(Content.value);
-    Out << "Declared in ";
+    Output.appendText("Declared in");
     // Drop trailing "::".
     if (!LocalScope.empty())
-      Out << *NamespaceScope << llvm::StringRef(LocalScope).drop_back(2);
+      Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2));
     else if (NamespaceScope->empty())
-      Out << "global namespace";
+      Output.appendInlineCode("global namespace");
     else
-      Out << llvm::StringRef(*NamespaceScope).drop_back(2);
-    Out << "\n\n";
+      Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
   }
 
   if (!Definition.empty()) {
-    Output.push_back(Definition);
+    Output.appendCodeBlock(Definition);
   } else {
     // Builtin types
-    Output.push_back(Name);
+    Output.appendCodeBlock(Name);
   }
-  Content.value += llvm::join(Output, " ");
-  return Content;
+  return Output;
 }
 
 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
index 3695989..180e3f7 100644 (file)
@@ -14,6 +14,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XREFS_H
 
 #include "ClangdUnit.h"
+#include "FormattedString.h"
 #include "Protocol.h"
 #include "index/Index.h"
 #include "index/SymbolLocation.h"
@@ -103,8 +104,8 @@ struct HoverInfo {
   /// Set for all templates(function, class, variable).
   llvm::Optional<std::vector<Param>> TemplateParameters;
 
-  /// Lower to LSP struct.
-  MarkupContent render() const;
+  /// Produce a user-readable information.
+  FormattedString present() const;
 };
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
 inline bool operator==(const HoverInfo::Param &LHS,
index 677f3f4..cf2d726 100644 (file)
@@ -893,7 +893,10 @@ TEST(Hover, All) {
               int test1 = bonjour;
             }
           )cpp",
-          "Declared in main\n\nint bonjour",
+          "text[Declared in]code[main]\n"
+          "codeblock(cpp) [\n"
+          "int bonjour\n"
+          "]",
       },
       {
           R"cpp(// Local variable in method
@@ -904,7 +907,10 @@ TEST(Hover, All) {
               }
             };
           )cpp",
-          "Declared in s::method\n\nint bonjour",
+          "text[Declared in]code[s::method]\n"
+          "codeblock(cpp) [\n"
+          "int bonjour\n"
+          "]",
       },
       {
           R"cpp(// Struct
@@ -915,7 +921,10 @@ TEST(Hover, All) {
               ns1::My^Class* Params;
             }
           )cpp",
-          "Declared in ns1\n\nstruct MyClass {}",
+          "text[Declared in]code[ns1]\n"
+          "codeblock(cpp) [\n"
+          "struct MyClass {}\n"
+          "]",
       },
       {
           R"cpp(// Class
@@ -926,7 +935,10 @@ TEST(Hover, All) {
               ns1::My^Class* Params;
             }
           )cpp",
-          "Declared in ns1\n\nclass MyClass {}",
+          "text[Declared in]code[ns1]\n"
+          "codeblock(cpp) [\n"
+          "class MyClass {}\n"
+          "]",
       },
       {
           R"cpp(// Union
@@ -937,7 +949,10 @@ TEST(Hover, All) {
               ns1::My^Union Params;
             }
           )cpp",
-          "Declared in ns1\n\nunion MyUnion {}",
+          "text[Declared in]code[ns1]\n"
+          "codeblock(cpp) [\n"
+          "union MyUnion {}\n"
+          "]",
       },
       {
           R"cpp(// Function definition via pointer
@@ -946,7 +961,10 @@ TEST(Hover, All) {
               auto *X = &^foo;
             }
           )cpp",
-          "Declared in global namespace\n\nint foo(int)",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "int foo(int)\n"
+          "]",
       },
       {
           R"cpp(// Function declaration via call
@@ -955,7 +973,10 @@ TEST(Hover, All) {
               return ^foo(42);
             }
           )cpp",
-          "Declared in global namespace\n\nint foo(int)",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "int foo(int)\n"
+          "]",
       },
       {
           R"cpp(// Field
@@ -965,7 +986,10 @@ TEST(Hover, All) {
               bar.^x;
             }
           )cpp",
-          "Declared in Foo\n\nint x",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x\n"
+          "]",
       },
       {
           R"cpp(// Field with initialization
@@ -975,7 +999,10 @@ TEST(Hover, All) {
               bar.^x;
             }
           )cpp",
-          "Declared in Foo\n\nint x = 5",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x = 5\n"
+          "]",
       },
       {
           R"cpp(// Static field
@@ -984,7 +1011,10 @@ TEST(Hover, All) {
               Foo::^x;
             }
           )cpp",
-          "Declared in Foo\n\nstatic int x",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "static int x\n"
+          "]",
       },
       {
           R"cpp(// Field, member initializer
@@ -993,7 +1023,10 @@ TEST(Hover, All) {
               Foo() : ^x(0) {}
             };
           )cpp",
-          "Declared in Foo\n\nint x",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x\n"
+          "]",
       },
       {
           R"cpp(// Field, GNU old-style field designator
@@ -1002,7 +1035,10 @@ TEST(Hover, All) {
               Foo bar = { ^x : 1 };
             }
           )cpp",
-          "Declared in Foo\n\nint x",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x\n"
+          "]",
       },
       {
           R"cpp(// Field, field designator
@@ -1011,7 +1047,10 @@ TEST(Hover, All) {
               Foo bar = { .^x = 2 };
             }
           )cpp",
-          "Declared in Foo\n\nint x",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x\n"
+          "]",
       },
       {
           R"cpp(// Method call
@@ -1021,7 +1060,10 @@ TEST(Hover, All) {
               bar.^x();
             }
           )cpp",
-          "Declared in Foo\n\nint x()",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "int x()\n"
+          "]",
       },
       {
           R"cpp(// Static method call
@@ -1030,7 +1072,10 @@ TEST(Hover, All) {
               Foo::^x();
             }
           )cpp",
-          "Declared in Foo\n\nstatic int x()",
+          "text[Declared in]code[Foo]\n"
+          "codeblock(cpp) [\n"
+          "static int x()\n"
+          "]",
       },
       {
           R"cpp(// Typedef
@@ -1039,7 +1084,10 @@ TEST(Hover, All) {
               ^Foo bar;
             }
           )cpp",
-          "Declared in global namespace\n\ntypedef int Foo",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "typedef int Foo\n"
+          "]",
       },
       {
           R"cpp(// Namespace
@@ -1048,7 +1096,10 @@ TEST(Hover, All) {
             } // namespace ns
             int main() { ^ns::Foo::bar(); }
           )cpp",
-          "Declared in global namespace\n\nnamespace ns {}",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "namespace ns {}\n"
+          "]",
       },
       {
           R"cpp(// Anonymous namespace
@@ -1059,7 +1110,10 @@ TEST(Hover, All) {
             } // namespace ns
             int main() { ns::f^oo++; }
           )cpp",
-          "Declared in ns::(anonymous)\n\nint foo",
+          "text[Declared in]code[ns::(anonymous)]\n"
+          "codeblock(cpp) [\n"
+          "int foo\n"
+          "]",
       },
       {
           R"cpp(// Macro
@@ -1069,14 +1123,18 @@ TEST(Hover, All) {
             #define MACRO 2
             #undef macro
           )cpp",
-          "#define MACRO 1",
+          "codeblock(cpp) [\n"
+          "#define MACRO 1\n"
+          "]",
       },
       {
           R"cpp(// Macro
             #define MACRO 0
             #define MACRO2 ^MACRO
           )cpp",
-          "#define MACRO 0",
+          "codeblock(cpp) [\n"
+          "#define MACRO 0\n"
+          "]",
       },
       {
           R"cpp(// Macro
@@ -1085,8 +1143,10 @@ TEST(Hover, All) {
             }
             int main() ^MACRO
           )cpp",
-          "#define MACRO                                                       "
-          "           \\\n  { return 0; }",
+          R"cpp(codeblock(cpp) [
+#define MACRO                                                                  \
+  { return 0; }
+])cpp",
       },
       {
           R"cpp(// Forward class declaration
@@ -1094,7 +1154,10 @@ TEST(Hover, All) {
             class Foo {};
             F^oo* foo();
           )cpp",
-          "Declared in global namespace\n\nclass Foo {}",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "class Foo {}\n"
+          "]",
       },
       {
           R"cpp(// Function declaration
@@ -1102,7 +1165,10 @@ TEST(Hover, All) {
             void g() { f^oo(); }
             void foo() {}
           )cpp",
-          "Declared in global namespace\n\nvoid foo()",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "void foo()\n"
+          "]",
       },
       {
           R"cpp(// Enum declaration
@@ -1113,7 +1179,10 @@ TEST(Hover, All) {
               Hel^lo hello = ONE;
             }
           )cpp",
-          "Declared in global namespace\n\nenum Hello {}",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "enum Hello {}\n"
+          "]",
       },
       {
           R"cpp(// Enumerator
@@ -1124,7 +1193,10 @@ TEST(Hover, All) {
               Hello hello = O^NE;
             }
           )cpp",
-          "Declared in Hello\n\nONE",
+          "text[Declared in]code[Hello]\n"
+          "codeblock(cpp) [\n"
+          "ONE\n"
+          "]",
       },
       {
           R"cpp(// Enumerator in anonymous enum
@@ -1135,7 +1207,10 @@ TEST(Hover, All) {
               int hello = O^NE;
             }
           )cpp",
-          "Declared in global namespace\n\nONE",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "ONE\n"
+          "]",
       },
       {
           R"cpp(// Global variable
@@ -1144,7 +1219,10 @@ TEST(Hover, All) {
               he^y++;
             }
           )cpp",
-          "Declared in global namespace\n\nstatic int hey = 10",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "static int hey = 10\n"
+          "]",
       },
       {
           R"cpp(// Global variable in namespace
@@ -1155,7 +1233,10 @@ TEST(Hover, All) {
               ns1::he^y++;
             }
           )cpp",
-          "Declared in ns1\n\nstatic int hey = 10",
+          "text[Declared in]code[ns1]\n"
+          "codeblock(cpp) [\n"
+          "static int hey = 10\n"
+          "]",
       },
       {
           R"cpp(// Field in anonymous struct
@@ -1166,7 +1247,10 @@ TEST(Hover, All) {
               s.he^llo++;
             }
           )cpp",
-          "Declared in (anonymous struct)\n\nint hello",
+          "text[Declared in]code[(anonymous struct)]\n"
+          "codeblock(cpp) [\n"
+          "int hello\n"
+          "]",
       },
       {
           R"cpp(// Templated function
@@ -1176,7 +1260,10 @@ TEST(Hover, All) {
             }
             void g() { auto x = f^oo<int>(); }
           )cpp",
-          "Declared in global namespace\n\ntemplate <typename T> T foo()",
+          "text[Declared in]code[global namespace]\n"
+          "codeblock(cpp) [\n"
+          "template <typename T> T foo()\n"
+          "]",
       },
       {
           R"cpp(// Anonymous union
@@ -1187,7 +1274,10 @@ TEST(Hover, All) {
             };
             void g() { struct outer o; o.v.d^ef++; }
           )cpp",
-          "Declared in outer::(anonymous union)\n\nint def",
+          "text[Declared in]code[outer::(anonymous union)]\n"
+          "codeblock(cpp) [\n"
+          "int def\n"
+          "]",
       },
       {
           R"cpp(// Nothing
@@ -1203,7 +1293,9 @@ TEST(Hover, All) {
               ^auto i = 1;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with const auto
@@ -1211,7 +1303,9 @@ TEST(Hover, All) {
               const ^auto i = 1;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with const auto&
@@ -1219,7 +1313,9 @@ TEST(Hover, All) {
               const ^auto& i = 1;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with auto&
@@ -1227,7 +1323,9 @@ TEST(Hover, All) {
               ^auto& i = 1;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with auto*
@@ -1236,7 +1334,9 @@ TEST(Hover, All) {
               ^auto* i = &a;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Auto with initializer list.
@@ -1249,7 +1349,9 @@ TEST(Hover, All) {
               ^auto i = {1,2};
             }
           )cpp",
-          "class std::initializer_list<int>",
+          "codeblock(cpp) [\n"
+          "class std::initializer_list<int>\n"
+          "]",
       },
       {
           R"cpp(// User defined conversion to auto
@@ -1257,7 +1359,9 @@ TEST(Hover, All) {
               operator ^auto() const { return 10; }
             };
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with decltype(auto)
@@ -1265,7 +1369,9 @@ TEST(Hover, All) {
               ^decltype(auto) i = 1;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with const decltype(auto)
@@ -1274,7 +1380,9 @@ TEST(Hover, All) {
               ^decltype(auto) i = j;
             }
           )cpp",
-          "const int",
+          "codeblock(cpp) [\n"
+          "const int\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with const& decltype(auto)
@@ -1284,7 +1392,9 @@ TEST(Hover, All) {
               ^decltype(auto) i = j;
             }
           )cpp",
-          "const int &",
+          "codeblock(cpp) [\n"
+          "const int &\n"
+          "]",
       },
       {
           R"cpp(// Simple initialization with & decltype(auto)
@@ -1294,7 +1404,9 @@ TEST(Hover, All) {
               ^decltype(auto) i = j;
             }
           )cpp",
-          "int &",
+          "codeblock(cpp) [\n"
+          "int &\n"
+          "]",
       },
       {
           R"cpp(// decltype with initializer list: nothing
@@ -1315,7 +1427,9 @@ TEST(Hover, All) {
               return 0;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// auto function return with trailing type
@@ -1324,7 +1438,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// trailing return type
@@ -1333,7 +1449,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// auto in function return
@@ -1342,7 +1460,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// auto& in function return
@@ -1351,7 +1471,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// auto* in function return
@@ -1361,7 +1483,9 @@ TEST(Hover, All) {
               return bar;
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// const auto& in function return
@@ -1370,7 +1494,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// decltype(auto) in function return
@@ -1379,7 +1505,9 @@ TEST(Hover, All) {
               return Bar();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// decltype(auto) reference in function return
@@ -1389,7 +1517,9 @@ TEST(Hover, All) {
               return (a);
             }
           )cpp",
-          "int &",
+          "codeblock(cpp) [\n"
+          "int &\n"
+          "]",
       },
       {
           R"cpp(// decltype lvalue reference
@@ -1398,7 +1528,9 @@ TEST(Hover, All) {
               ^decltype(I) J = I;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// decltype lvalue reference
@@ -1408,7 +1540,9 @@ TEST(Hover, All) {
               ^decltype(K) J = I;
             }
           )cpp",
-          "int &",
+          "codeblock(cpp) [\n"
+          "int &\n"
+          "]",
       },
       {
           R"cpp(// decltype lvalue reference parenthesis
@@ -1417,7 +1551,9 @@ TEST(Hover, All) {
               ^decltype((I)) J = I;
             }
           )cpp",
-          "int &",
+          "codeblock(cpp) [\n"
+          "int &\n"
+          "]",
       },
       {
           R"cpp(// decltype rvalue reference
@@ -1426,7 +1562,9 @@ TEST(Hover, All) {
               ^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I);
             }
           )cpp",
-          "int &&",
+          "codeblock(cpp) [\n"
+          "int &&\n"
+          "]",
       },
       {
           R"cpp(// decltype rvalue reference function call
@@ -1436,7 +1574,9 @@ TEST(Hover, All) {
               ^decltype(bar()) J = bar();
             }
           )cpp",
-          "int &&",
+          "codeblock(cpp) [\n"
+          "int &&\n"
+          "]",
       },
       {
           R"cpp(// decltype of function with trailing return type.
@@ -1448,7 +1588,9 @@ TEST(Hover, All) {
               ^decltype(test()) i = test();
             }
           )cpp",
-          "struct Bar",
+          "codeblock(cpp) [\n"
+          "struct Bar\n"
+          "]",
       },
       {
           R"cpp(// decltype of var with decltype.
@@ -1458,7 +1600,9 @@ TEST(Hover, All) {
               ^decltype(J) K = J;
             }
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
       {
           R"cpp(// structured binding. Not supported yet
@@ -1486,7 +1630,9 @@ TEST(Hover, All) {
             int bar();
             ^auto (*foo)() = bar;
           )cpp",
-          "int",
+          "codeblock(cpp) [\n"
+          "int\n"
+          "]",
       },
   };
 
@@ -1497,7 +1643,8 @@ TEST(Hover, All) {
     auto AST = TU.build();
     if (auto H = getHover(AST, T.point(), format::getLLVMStyle())) {
       EXPECT_NE("", Test.ExpectedHover) << Test.Input;
-      EXPECT_EQ(H->render().value, Test.ExpectedHover.str()) << Test.Input;
+      EXPECT_EQ(H->present().renderForTests(), Test.ExpectedHover.str())
+          << Test.Input;
     } else
       EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
   }