ilya-biryukov updated this revision to Diff 197991.
ilya-biryukov added a comment.

- Remove a FIXME that was fixed


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58547/new/

https://reviews.llvm.org/D58547

Files:
  clang-tools-extra/clangd/CMakeLists.txt
  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/CMakeLists.txt
  clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
  clang-tools-extra/clangd/unittests/XRefsTests.cpp

Index: clang-tools-extra/clangd/unittests/XRefsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1172,7 +1172,9 @@
     auto AST = TU.build();
     if (auto H = getHover(AST, T.point())) {
       EXPECT_NE("", Test.ExpectedHover) << Test.Input;
-      EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input;
+      // FIXME: add renderForTests() and use it here.
+      EXPECT_EQ(H->Content.renderAsPlainText(), Test.ExpectedHover.str())
+          << Test.Input;
     } else
       EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
   }
Index: clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
@@ -0,0 +1,158 @@
+//===-- FormattedStringTests.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "FormattedString.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringRef.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+TEST(FormattedString, Basic) {
+  FormattedString S;
+  EXPECT_EQ(S.renderAsPlainText(), "");
+  EXPECT_EQ(S.renderAsMarkdown(), "");
+
+  S.appendText("foobar");
+  EXPECT_EQ(S.renderAsPlainText(), "foobar");
+  EXPECT_EQ(S.renderAsMarkdown(), "foobar");
+
+  S = FormattedString();
+  S.appendInlineCode("foobar");
+  EXPECT_EQ(S.renderAsPlainText(), "foobar");
+  EXPECT_EQ(S.renderAsMarkdown(), "`foobar`");
+
+  S = FormattedString();
+  S.appendCodeBlock("foobar");
+  EXPECT_EQ(S.renderAsPlainText(), "foobar");
+  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "foobar\n"
+                                  "```\n");
+
+  S = FormattedString();
+}
+
+TEST(FormattedString, CodeBlocks) {
+  FormattedString S;
+  S.appendCodeBlock("foobar");
+  S.appendCodeBlock("bazqux", "javascript");
+
+  EXPECT_EQ(S.renderAsPlainText(), "foobar\n\n\nbazqux");
+  std::string ExpectedMarkdown = R"md(```cpp
+foobar
+```
+```javascript
+bazqux
+```
+)md";
+  EXPECT_EQ(S.renderAsMarkdown(), ExpectedMarkdown);
+
+  S = FormattedString();
+  S.appendInlineCode("foobar");
+  S.appendInlineCode("bazqux");
+  EXPECT_EQ(S.renderAsPlainText(), "foobar bazqux");
+  EXPECT_EQ(S.renderAsMarkdown(), "`foobar` `bazqux`");
+
+  S = FormattedString();
+  S.appendText("foo");
+  S.appendInlineCode("bar");
+  S.appendText("baz");
+
+  EXPECT_EQ(S.renderAsPlainText(), "foo bar baz");
+  EXPECT_EQ(S.renderAsMarkdown(), "foo`bar`baz");
+}
+
+TEST(FormattedString, Escaping) {
+  // Check some ASCII punctuation
+  FormattedString S;
+  S.appendText("*!`");
+  EXPECT_EQ(S.renderAsMarkdown(), "\\*\\!\\`");
+
+  // Check all ASCII punctuation.
+  S = FormattedString();
+  std::string Punctuation = R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
+  // Same text, with each character escaped.
+  std::string EscapedPunctuation;
+  EscapedPunctuation.reserve(2 * Punctuation.size());
+  for (char C : Punctuation)
+    EscapedPunctuation += std::string("\\") + C;
+  S.appendText(Punctuation);
+  EXPECT_EQ(S.renderAsMarkdown(), EscapedPunctuation);
+
+  // In code blocks we don't need to escape ASCII punctuation.
+  S = FormattedString();
+  S.appendInlineCode("* foo !+ bar * baz");
+  EXPECT_EQ(S.renderAsMarkdown(), "`* foo !+ bar * baz`");
+  S = FormattedString();
+  S.appendCodeBlock("#define FOO\n* foo !+ bar * baz");
+  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "#define FOO\n* foo !+ bar * baz\n"
+                                  "```\n");
+
+  // But we have to escape the backticks.
+  S = FormattedString();
+  S.appendInlineCode("foo`bar`baz");
+  EXPECT_EQ(S.renderAsMarkdown(), "`foo``bar``baz`");
+
+  S = FormattedString();
+  S.appendCodeBlock("foo`bar`baz");
+  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "foo`bar`baz\n"
+                                  "```\n");
+
+  // Inline code blocks starting or ending with backticks should add spaces.
+  S = FormattedString();
+  S.appendInlineCode("`foo");
+  EXPECT_EQ(S.renderAsMarkdown(), "` ``foo `");
+  S = FormattedString();
+  S.appendInlineCode("foo`");
+  EXPECT_EQ(S.renderAsMarkdown(), "` foo`` `");
+  S = FormattedString();
+  S.appendInlineCode("`foo`");
+  EXPECT_EQ(S.renderAsMarkdown(), "` ``foo`` `");
+
+  // Should also add extra spaces if the block stars and ends with spaces.
+  S = FormattedString();
+  S.appendInlineCode(" foo ");
+  EXPECT_EQ(S.renderAsMarkdown(), "`  foo  `");
+  S = FormattedString();
+  S.appendInlineCode("foo ");
+  EXPECT_EQ(S.renderAsMarkdown(), "`foo `");
+  S = FormattedString();
+  S.appendInlineCode(" foo");
+  EXPECT_EQ(S.renderAsMarkdown(), "` foo`");
+
+  // Code blocks might need more than 3 backticks.
+  S = FormattedString();
+  S.appendCodeBlock("foobarbaz `\nqux");
+  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "foobarbaz `\nqux\n"
+                                  "```\n");
+  S = FormattedString();
+  S.appendCodeBlock("foobarbaz ``\nqux");
+  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "foobarbaz ``\nqux\n"
+                                  "```\n");
+  S = FormattedString();
+  S.appendCodeBlock("foobarbaz ```\nqux");
+  EXPECT_EQ(S.renderAsMarkdown(), "````cpp\n"
+                                  "foobarbaz ```\nqux\n"
+                                  "````\n");
+  S = FormattedString();
+  S.appendCodeBlock("foobarbaz ` `` ``` ```` `\nqux");
+  EXPECT_EQ(S.renderAsMarkdown(), "`````cpp\n"
+                                  "foobarbaz ` `` ``` ```` `\nqux\n"
+                                  "`````\n");
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/unittests/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -38,6 +38,7 @@
   FileDistanceTests.cpp
   FileIndexTests.cpp
   FindSymbolsTests.cpp
+  FormattedStringTests.cpp
   FSTests.cpp
   FunctionTests.cpp
   FuzzyMatchTests.cpp
Index: clang-tools-extra/clangd/XRefs.h
===================================================================
--- clang-tools-extra/clangd/XRefs.h
+++ clang-tools-extra/clangd/XRefs.h
@@ -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 "llvm/ADT/Optional.h"
@@ -46,8 +47,12 @@
 std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
                                                       Position Pos);
 
+struct HoverInfo {
+  FormattedString Content;
+  llvm::Optional<Range> Range;
+};
 /// Get the hover information when hovering at \p Pos.
-llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos);
+llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos);
 
 /// Returns reference locations of the symbol at a specified \p Pos.
 /// \p Limit limits the number of results returned (0 means no limit).
Index: clang-tools-extra/clangd/XRefs.cpp
===================================================================
--- clang-tools-extra/clangd/XRefs.cpp
+++ clang-tools-extra/clangd/XRefs.cpp
@@ -8,6 +8,7 @@
 #include "XRefs.h"
 #include "AST.h"
 #include "FindSymbols.h"
+#include "FormattedString.h"
 #include "Logger.h"
 #include "SourceCode.h"
 #include "URI.h"
@@ -523,17 +524,16 @@
 }
 
 /// Generate a \p Hover object given the declaration \p D.
-static Hover getHoverContents(const Decl *D) {
-  Hover H;
+static FormattedString getHoverContents(const Decl *D) {
+  FormattedString R;
   llvm::Optional<std::string> NamedScope = getScopeName(D);
 
   // Generate the "Declared in" section.
   if (NamedScope) {
     assert(!NamedScope->empty());
 
-    H.contents.value += "Declared in ";
-    H.contents.value += *NamedScope;
-    H.contents.value += "\n\n";
+    R.appendText("Declared in ");
+    R.appendInlineCode(*NamedScope);
   }
 
   // We want to include the template in the Hover.
@@ -545,29 +545,26 @@
 
   PrintingPolicy Policy =
       printingPolicyForDecls(D->getASTContext().getPrintingPolicy());
-
   D->print(OS, Policy);
 
-  OS.flush();
-
-  H.contents.value += DeclText;
-  return H;
+  R.appendCodeBlock(OS.str());
+  return R;
 }
 
 /// Generate a \p Hover object given the type \p T.
-static Hover getHoverContents(QualType T, ASTContext &ASTCtx) {
-  Hover H;
-  std::string TypeText;
-  llvm::raw_string_ostream OS(TypeText);
+static FormattedString getHoverContents(QualType T, ASTContext &ASTCtx) {
+  std::string Code;
+  llvm::raw_string_ostream OS(Code);
   PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy());
   T.print(OS, Policy);
-  OS.flush();
-  H.contents.value += TypeText;
-  return H;
+
+  FormattedString R;
+  R.appendCodeBlock(OS.str());
+  return R;
 }
 
 /// Generate a \p Hover object given the macro \p MacroDecl.
-static Hover getHoverContents(MacroDecl Decl, ParsedAST &AST) {
+static FormattedString getHoverContents(MacroDecl Decl, ParsedAST &AST) {
   SourceManager &SM = AST.getASTContext().getSourceManager();
   std::string Definition = Decl.Name;
 
@@ -587,10 +584,9 @@
     }
   }
 
-  Hover H;
-  H.contents.kind = MarkupKind::PlainText;
-  H.contents.value = "#define " + Definition;
-  return H;
+  FormattedString S;
+  S.appendCodeBlock("#define " + std::move(Definition));
+  return S;
 }
 
 namespace {
@@ -705,22 +701,31 @@
   return V.getDeducedType();
 }
 
-llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos) {
+llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos) {
   const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
   SourceLocation SourceLocationBeg =
       getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
   // Identified symbols at a specific position.
   auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg);
 
-  if (!Symbols.Macros.empty())
-    return getHoverContents(Symbols.Macros[0], AST);
+  if (!Symbols.Macros.empty()) {
+    HoverInfo H;
+    H.Content = getHoverContents(Symbols.Macros[0], AST);
+    return H;
+  }
 
-  if (!Symbols.Decls.empty())
-    return getHoverContents(Symbols.Decls[0]);
+  if (!Symbols.Decls.empty()) {
+    HoverInfo H;
+    H.Content = getHoverContents(Symbols.Decls[0]);
+    return H;
+  }
 
   auto DeducedType = getDeducedType(AST, SourceLocationBeg);
-  if (DeducedType && !DeducedType->isNull())
-    return getHoverContents(*DeducedType, AST.getASTContext());
+  if (DeducedType && !DeducedType->isNull()) {
+    HoverInfo H;
+    H.Content = getHoverContents(*DeducedType, AST.getASTContext());
+    return H;
+  }
 
   return None;
 }
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ clang-tools-extra/clangd/Protocol.h
@@ -353,6 +353,15 @@
 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 @@
 
   /// 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,13 +873,8 @@
 };
 bool fromJSON(const llvm::json::Value &, CompletionParams &);
 
-enum class MarkupKind {
-  PlainText,
-  Markdown,
-};
-
 struct MarkupContent {
-  MarkupKind kind = MarkupKind::PlainText;
+  MarkupKind kind = MarkupKind::Plaintext;
   std::string value;
 };
 llvm::json::Value toJSON(const MarkupContent &MC);
Index: clang-tools-extra/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ clang-tools-extra/clangd/Protocol.cpp
@@ -17,6 +17,7 @@
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/JSON.h"
@@ -302,6 +303,17 @@
               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")) {
@@ -675,7 +687,7 @@
 
 static llvm::StringRef toTextKind(MarkupKind Kind) {
   switch (Kind) {
-  case MarkupKind::PlainText:
+  case MarkupKind::Plaintext:
     return "plaintext";
   case MarkupKind::Markdown:
     return "markdown";
@@ -683,6 +695,23 @@
   llvm_unreachable("Invalid MarkupKind");
 }
 
+bool fromJSON(const llvm::json::Value &V, MarkupKind &K) {
+  auto Str = V.getAsString();
+  if (!Str)
+    return false;
+  if (*Str == "plaintext")
+    K = MarkupKind::Plaintext;
+  else if (*Str == "markdown")
+    K = MarkupKind::Markdown;
+  else
+    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: clang-tools-extra/clangd/FormattedString.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/FormattedString.h
@@ -0,0 +1,58 @@
+//===--- FormattedString.h ----------------------------------*- C++-*------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// A simple intermediate representation of formatted text that could be
+// converted to plaintext or markdown.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
+
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace clangd {
+
+/// A structured string representation that could be converted to markdown or
+/// plaintext upon requrest.
+class FormattedString {
+public:
+  /// Append plain text to the end of the string.
+  void appendText(std::string Text);
+  /// Append a block of C++ code. This translates to a ``` block in markdown.
+  /// In a plain text representation, the code block will be surrounded by
+  /// newlines.
+  void appendCodeBlock(std::string Code, std::string Language = "cpp");
+  /// Append an inline block of C++ code. This translates to the ` block in
+  /// markdown.
+  /// EXPECTS: Code does not contain newlines.
+  void appendInlineCode(std::string Code);
+
+  std::string renderAsMarkdown() const;
+  std::string renderAsPlainText() const;
+
+private:
+  enum class ChunkKind {
+    PlainText,       /// A plain text paragraph.
+    CodeBlock,       /// A block of code.
+    InlineCodeBlock, /// An inline block of code.
+  };
+  struct Chunk {
+    ChunkKind Kind = ChunkKind::PlainText;
+    std::string Contents;
+    /// Language for code block chunks. Ignored for other chunks.
+    std::string Language;
+  };
+  std::vector<Chunk> Chunks;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/clangd/FormattedString.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/FormattedString.cpp
@@ -0,0 +1,176 @@
+//===--- FormattedString.cpp --------------------------------*- C++-*------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "FormattedString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <cstddef>
+#include <string>
+
+namespace clang {
+namespace clangd {
+
+namespace {
+/// Escape a markdown text block. Ensures the punctuation will not introduce
+/// any of the markdown constructs.
+static std::string renderText(llvm::StringRef Input) {
+  // Escaping ASCII punctiation ensures we can't start a markdown construct.
+  constexpr llvm::StringLiteral Punctuation =
+      R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
+
+  std::string R;
+  for (size_t From = 0; From < Input.size();) {
+    size_t Next = Input.find_first_of(Punctuation, From);
+    R += Input.substr(From, Next - From);
+    if (Next == llvm::StringRef::npos)
+      break;
+    R += "\\";
+    R += Input[Next];
+
+    From = Next + 1;
+  }
+  return R;
+}
+
+/// Renders \p Input as an inline block of code in markdown. The returned value
+/// is surrounded by backticks and the inner contents are properly escaped.
+static std::string renderInlineBlock(llvm::StringRef Input) {
+  std::string R;
+  // Double all backticks to make sure we don't close the inline block early.
+  for (size_t From = 0; From < Input.size();) {
+    size_t Next = Input.find("`", From);
+    R += Input.substr(From, Next - From);
+    if (Next == llvm::StringRef::npos)
+      break;
+    R += "``"; // double the found backtick.
+
+    From = Next + 1;
+  }
+  // If results starts with a backtick, add spaces on both sides. The spaces
+  // are ignored by markdown renderers.
+  if (llvm::StringRef(R).startswith("`") || llvm::StringRef(R).endswith("`"))
+    return "` " + std::move(R) + " `";
+  // Markdown render should ignore first and last space if both are there. We
+  // add an extra pair of spaces in that case to make sure we render what the
+  // user intended.
+  if (llvm::StringRef(R).startswith(" ") && llvm::StringRef(R).endswith(" "))
+    return "` " + std::move(R) + " `";
+  return "`" + std::move(R) + "`";
+}
+/// Render \p Input as markdown code block with a specified \p Language. The
+/// result is surrounded by >= 3 backticks. Although markdown also allows to use
+/// '~' for code blocks, they are never used.
+static std::string renderCodeBlock(llvm::StringRef Input,
+                                   llvm::StringRef Language) {
+  // Count the maximum number of consecutive backticks in \p Input. We need to
+  // start and end the code block with more.
+  unsigned MaxBackticks = 0;
+  for (llvm::StringRef Left = Input;;) {
+    unsigned N = Left.find("`");
+    Left = Left.substr(N);
+    if (Left.empty())
+      break;
+    unsigned Backticks = 0;
+    for (; !Left.empty() && Left.front() == '`'; Left = Left.drop_front())
+      ++Backticks;
+    MaxBackticks = std::max(Backticks, MaxBackticks);
+  }
+  // Use the corresponding number of backticks to start and end a code block.
+  std::string BlockMarker(/*Repeat=*/std::max(3u, MaxBackticks + 1), '`');
+  return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker;
+}
+
+} // namespace
+
+void FormattedString::appendText(std::string Text) {
+  if (Chunks.empty() || Chunks.back().Kind != ChunkKind::PlainText) {
+    Chunk C;
+    C.Kind = ChunkKind::PlainText;
+    Chunks.push_back(C);
+  }
+  Chunks.back().Contents += Text;
+}
+
+void FormattedString::appendCodeBlock(std::string Code, std::string Language) {
+  Chunk C;
+  C.Kind = ChunkKind::CodeBlock;
+  C.Contents = std::move(Code);
+  C.Language = std::move(Language);
+  Chunks.push_back(std::move(C));
+}
+
+void FormattedString::appendInlineCode(std::string Code) {
+  assert(!llvm::StringRef(Code).contains("\n"));
+
+  Chunk C;
+  C.Kind = ChunkKind::InlineCodeBlock;
+  C.Contents = std::move(Code);
+  Chunks.push_back(std::move(C));
+}
+
+std::string FormattedString::renderAsMarkdown() const {
+  std::string R;
+  for (const auto &C : Chunks) {
+    switch (C.Kind) {
+    case ChunkKind::PlainText:
+      R += renderText(C.Contents);
+      continue;
+    case ChunkKind::InlineCodeBlock:
+      // Make sure we don't glue two backticks together.
+      if (llvm::StringRef(R).endswith("`"))
+        R += " ";
+      R += renderInlineBlock(C.Contents);
+      continue;
+    case ChunkKind::CodeBlock:
+      if (!R.empty() && !llvm::StringRef(R).endswith("\n"))
+        R += "\n";
+      R += renderCodeBlock(C.Contents, C.Language);
+      R += "\n";
+      continue;
+    }
+    llvm_unreachable("unhanlded ChunkKind");
+  }
+  return R;
+}
+
+static bool IsWhitespace(char C) {
+  return llvm::StringLiteral(" \t\n").contains(C);
+}
+
+std::string FormattedString::renderAsPlainText() const {
+  std::string R;
+  auto EnsureWhitespace = [&]() {
+    if (R.empty() || IsWhitespace(R.back()))
+      return;
+    R += " ";
+  };
+  for (const auto &C : Chunks) {
+    switch (C.Kind) {
+    case ChunkKind::PlainText:
+      EnsureWhitespace();
+      R += C.Contents;
+      continue;
+    case ChunkKind::InlineCodeBlock:
+      EnsureWhitespace();
+      R += C.Contents;
+      continue;
+    case ChunkKind::CodeBlock:
+      if (!R.empty())
+        R += "\n\n";
+      R += C.Contents;
+      if (!llvm::StringRef(C.Contents).endswith("\n"))
+        R += "\n";
+      continue;
+    }
+    llvm_unreachable("unhanlded ChunkKind");
+  }
+  while (!R.empty() && IsWhitespace(R.back()))
+    R.pop_back();
+  return R;
+}
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/ClangdServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdServer.h
+++ clang-tools-extra/clangd/ClangdServer.h
@@ -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"
@@ -180,7 +181,7 @@
 
   /// Get code hover for a given position.
   void findHover(PathRef File, Position Pos,
-                 Callback<llvm::Optional<Hover>> CB);
+                 Callback<llvm::Optional<HoverInfo>> CB);
 
   /// Get information about type hierarchy for a given position.
   void typeHierarchy(PathRef File, Position Pos, int Resolve,
Index: clang-tools-extra/clangd/ClangdServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdServer.cpp
+++ clang-tools-extra/clangd/ClangdServer.cpp
@@ -10,11 +10,13 @@
 #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"
@@ -527,14 +529,12 @@
 }
 
 void ClangdServer::findHover(PathRef File, Position Pos,
-                             Callback<llvm::Optional<Hover>> CB) {
-  auto Action = [Pos](Callback<llvm::Optional<Hover>> CB,
-                      llvm::Expected<InputsAndAST> InpAST) {
+                             Callback<llvm::Optional<HoverInfo>> CB) {
+  auto Action = [Pos](decltype(CB) CB, llvm::Expected<InputsAndAST> InpAST) {
     if (!InpAST)
       return CB(InpAST.takeError());
     CB(clangd::getHover(InpAST->AST, Pos));
   };
-
   WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
 }
 
Index: clang-tools-extra/clangd/ClangdLSPServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.h
+++ clang-tools-extra/clangd/ClangdLSPServer.h
@@ -154,7 +154,10 @@
   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: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -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 @@
   SupportsHierarchicalDocumentSymbol =
       Params.capabilities.HierarchicalDocumentSymbol;
   SupportFileStatus = Params.initializationOptions.FileStatus;
+  HoverContentFormat = Params.capabilities.HoverContentFormat;
   llvm::json::Object Result{
       {{"capabilities",
         llvm::json::Object{
@@ -839,7 +841,31 @@
 void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
                               Callback<llvm::Optional<Hover>> Reply) {
   Server->findHover(Params.textDocument.uri.file(), Params.position,
-                    std::move(Reply));
+                    Bind(
+                        [this](decltype(Reply) Reply,
+                               llvm::Expected<llvm::Optional<HoverInfo>> H) {
+                          if (!H)
+                            return Reply(H.takeError());
+                          if (!*H)
+                            return Reply(llvm::None);
+
+                          Hover R;
+                          switch (HoverContentFormat) {
+                          case MarkupKind::Plaintext:
+                            R.contents.kind = MarkupKind::Plaintext;
+                            R.contents.value =
+                                (*H)->Content.renderAsPlainText();
+                            R.range = (*H)->Range;
+                            return Reply(std::move(R));
+                          case MarkupKind::Markdown:
+                            R.contents.kind = MarkupKind::Markdown;
+                            R.contents.value = (*H)->Content.renderAsMarkdown();
+                            R.range = (*H)->Range;
+                            return Reply(std::move(R));
+                          };
+                          llvm_unreachable("unhandled MarkupKind");
+                        },
+                        std::move(Reply)));
 }
 
 void ClangdLSPServer::onTypeHierarchy(
Index: clang-tools-extra/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/CMakeLists.txt
+++ clang-tools-extra/clangd/CMakeLists.txt
@@ -50,6 +50,7 @@
   FileDistance.cpp
   FS.cpp
   FSProvider.cpp
+  FormattedString.cpp
   FuzzyMatch.cpp
   GlobalCompilationDatabase.cpp
   Headers.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to