kadircet updated this revision to Diff 232332.
kadircet added a comment.

- Move function definitions to source file, via define out-of-line :)


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D71063

Files:
  clang-tools-extra/clangd/FormattedString.cpp
  clang-tools-extra/clangd/FormattedString.h
  clang-tools-extra/clangd/Hover.cpp
  clang-tools-extra/clangd/Hover.h
  clang-tools-extra/clangd/unittests/FormattedStringTests.cpp

Index: clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
+++ clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
@@ -16,184 +16,236 @@
 namespace clangd {
 namespace {
 
-TEST(FormattedString, Basic) {
-  FormattedString S;
-  EXPECT_EQ(S.renderAsPlainText(), "");
-  EXPECT_EQ(S.renderAsMarkdown(), "");
-
-  S.appendText("foobar  ");
-  S.appendText("baz");
-  EXPECT_EQ(S.renderAsPlainText(), "foobar baz");
-  EXPECT_EQ(S.renderAsMarkdown(), "foobar  baz");
-
-  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");
+TEST(Paragraph, Basics) {
+  std::string Expected;
+  Paragraph P;
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendText("foobar");
+  Expected += "text[foobar]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendText("baz");
+  Expected += "text[baz]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendInlineCode("foo");
+  Expected += "code[foo]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendInlineCode("bar");
+  Expected += "code[bar]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendCodeBlock("foobar");
+  Expected += "codeblock(cpp) [foobar]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendCodeBlock("bazqux", "javascript");
+  Expected += "codeblock(javascript) [bazqux]";
+  EXPECT_EQ(P.renderForTests(), Expected);
+
+  P.appendText("after");
+  Expected += "text[after]";
+  EXPECT_EQ(P.renderForTests(), Expected);
 }
 
-TEST(FormattedString, CodeBlocks) {
-  FormattedString S;
-  S.appendCodeBlock("foobar");
-  S.appendCodeBlock("bazqux", "javascript");
-  S.appendText("after");
+TEST(Paragraph, Render) {
+  struct Test {
+    enum {
+      PlainText,
+      InlineBlock,
+      CodeBlock,
+    } Kind;
+    llvm::StringRef Contents;
+    llvm::StringRef Language;
+    llvm::StringRef ExpectedMarkdown;
+    llvm::StringRef ExpectedPlainText;
+  } Tests[] = {
+      {
+          Test::CodeBlock,
+          "foobar",
+          "cpp",
+          R"md(```cpp
+foobar
+```)md",
+          "foobar",
+      },
+      {
+          Test::CodeBlock,
+          "bazqux",
+          "javascript",
+          R"md(```cpp
+foobar
+```
+```javascript
+bazqux
+```)md",
+          R"pt(foobar
+
+bazqux)pt",
+      },
+      {
+          Test::PlainText,
+          "after",
+          "",
+          R"md(```cpp
+foobar
+```
+```javascript
+bazqux
+```
+after)md",
+          R"pt(foobar
+
+bazqux
 
-  std::string ExpectedText = R"(foobar
+after)pt",
+      },
+      {
+          Test::InlineBlock,
+          "foobar",
+          "",
+          R"md(```cpp
+foobar
+```
+```javascript
+bazqux
+```
+after `foobar`)md",
+          R"pt(foobar
 
 bazqux
 
-after)";
-  EXPECT_EQ(S.renderAsPlainText(), ExpectedText);
-  std::string ExpectedMarkdown = R"md(```cpp
+after foobar)pt",
+      },
+      {
+          Test::PlainText,
+          "foo",
+          "",
+          R"md(```cpp
 foobar
 ```
 ```javascript
 bazqux
 ```
-after)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");
+after `foobar` foo)md",
+          R"pt(foobar
+
+bazqux
+
+after foobar foo)pt",
+      },
+  };
+  Paragraph P;
+  for (const auto &T : Tests) {
+    switch (T.Kind) {
+    case Test::CodeBlock:
+      P.appendCodeBlock(T.Contents, T.Language);
+      break;
+    case Test::InlineBlock:
+      P.appendInlineCode(T.Contents);
+      break;
+    case Test::PlainText:
+      P.appendText(T.Contents);
+      break;
+    }
+    EXPECT_EQ(P.renderAsMarkdown(), T.ExpectedMarkdown);
+    EXPECT_EQ(P.renderAsPlainText(), T.ExpectedPlainText);
+  }
 }
 
-TEST(FormattedString, Escaping) {
+TEST(Leaves, Escaping) {
   // Check some ASCII punctuation
-  FormattedString S;
-  S.appendText("*!`");
-  EXPECT_EQ(S.renderAsMarkdown(), "\\*\\!\\`");
+  Paragraph P;
+  P.appendText("*!`");
+  EXPECT_EQ(P.renderAsMarkdown(), "\\*\\!\\`");
 
   // Check all ASCII punctuation.
-  S = FormattedString();
+  P = Paragraph();
   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);
+  P.appendText(Punctuation);
+  EXPECT_EQ(P.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"
+  P = Paragraph();
+  P.appendInlineCode("* foo !+ bar * baz");
+  EXPECT_EQ(P.renderAsMarkdown(), "`* foo !+ bar * baz`");
+  P = Paragraph();
+  P.appendCodeBlock("#define FOO\n* foo !+ bar * baz");
+  EXPECT_EQ(P.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`");
+  P = Paragraph();
+  P.appendInlineCode("foo`bar`baz");
+  EXPECT_EQ(P.renderAsMarkdown(), "`foo``bar``baz`");
 
-  S = FormattedString();
-  S.appendCodeBlock("foo`bar`baz");
-  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+  P = Paragraph();
+  P.appendCodeBlock("foo`bar`baz");
+  EXPECT_EQ(P.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`");
+  P = Paragraph();
+  P.appendInlineCode("`foo");
+  EXPECT_EQ(P.renderAsMarkdown(), "` ``foo `");
+  P = Paragraph();
+  P.appendInlineCode("foo`");
+  EXPECT_EQ(P.renderAsMarkdown(), "` foo`` `");
+  P = Paragraph();
+  P.appendInlineCode("`foo`");
+  EXPECT_EQ(P.renderAsMarkdown(), "` ``foo`` `");
 
   // Code blocks might need more than 3 backticks.
-  S = FormattedString();
-  S.appendCodeBlock("foobarbaz `\nqux");
-  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+  P = Paragraph();
+  P.appendCodeBlock("foobarbaz `\nqux");
+  EXPECT_EQ(P.renderAsMarkdown(), "```cpp\n"
                                   "foobarbaz `\nqux\n"
-                                  "```\n");
-  S = FormattedString();
-  S.appendCodeBlock("foobarbaz ``\nqux");
-  EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
+                                  "```");
+  P = Paragraph();
+  P.appendCodeBlock("foobarbaz ``\nqux");
+  EXPECT_EQ(P.renderAsMarkdown(), "```cpp\n"
                                   "foobarbaz ``\nqux\n"
-                                  "```\n");
-  S = FormattedString();
-  S.appendCodeBlock("foobarbaz ```\nqux");
-  EXPECT_EQ(S.renderAsMarkdown(), "````cpp\n"
+                                  "```");
+  P = Paragraph();
+  P.appendCodeBlock("foobarbaz ```\nqux");
+  EXPECT_EQ(P.renderAsMarkdown(), "````cpp\n"
                                   "foobarbaz ```\nqux\n"
-                                  "````\n");
-  S = FormattedString();
-  S.appendCodeBlock("foobarbaz ` `` ``` ```` `\nqux");
-  EXPECT_EQ(S.renderAsMarkdown(), "`````cpp\n"
+                                  "````");
+  P = Paragraph();
+  P.appendCodeBlock("foobarbaz ` `` ``` ```` `\nqux");
+  EXPECT_EQ(P.renderAsMarkdown(), "`````cpp\n"
                                   "foobarbaz ` `` ``` ```` `\nqux\n"
-                                  "`````\n");
+                                  "`````");
 }
 
-TEST(FormattedString, MarkdownWhitespace) {
+TEST(Leaves, MarkdownWhitespace) {
   // Whitespace should be added as separators between blocks.
-  FormattedString S;
-  S.appendText("foo");
-  S.appendText("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo bar");
-
-  S = FormattedString();
-  S.appendInlineCode("foo");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "`foo` `bar`");
+  Paragraph P;
+  P.appendText("foo");
+  P.appendText("bar");
+  P.appendInlineCode("foo");
+  P.appendInlineCode("bar");
+  EXPECT_EQ(P.renderAsMarkdown(), "foo bar `foo` `bar`");
+  EXPECT_EQ(P.renderAsPlainText(), "foo bar foo bar");
 
   // However, we don't want to add any extra whitespace.
-  S = FormattedString();
-  S.appendText("foo ");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo `bar`");
-
-  S = FormattedString();
-  S.appendText("foo\n");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo\n`bar`");
-
-  S = FormattedString();
-  S.appendInlineCode("foo");
-  S.appendText(" bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "`foo` bar");
-
-  S = FormattedString();
-  S.appendText("foo");
-  S.appendCodeBlock("bar");
-  S.appendText("baz");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo\n```cpp\nbar\n```\nbaz");
+  P = Paragraph();
+  P.appendText("foo ");
+  P.appendInlineCode(" bar\n");
+  P.appendInlineCode("baz");
+  EXPECT_EQ(P.renderAsMarkdown(), "foo `bar` `baz`");
+  EXPECT_EQ(P.renderAsPlainText(), "foo bar baz");
 }
 
-
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/Hover.h
===================================================================
--- clang-tools-extra/clangd/Hover.h
+++ clang-tools-extra/clangd/Hover.h
@@ -12,6 +12,7 @@
 #include "FormattedString.h"
 #include "ParsedAST.h"
 #include "Protocol.h"
+#include "clang/Index/IndexSymbol.h"
 
 namespace clang {
 namespace clangd {
@@ -53,7 +54,7 @@
   /// Name of the symbol, does not contain any "::".
   std::string Name;
   llvm::Optional<Range> SymRange;
-  index::SymbolKind Kind;
+  index::SymbolKind Kind = index::SymbolKind::Unknown;
   std::string Documentation;
   /// Source code containing the definition of the symbol.
   std::string Definition;
@@ -71,7 +72,7 @@
   llvm::Optional<std::string> Value;
 
   /// Produce a user-readable information.
-  FormattedString present() const;
+  Document present() const;
 };
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
 inline bool operator==(const HoverInfo::Param &LHS,
Index: clang-tools-extra/clangd/Hover.cpp
===================================================================
--- clang-tools-extra/clangd/Hover.cpp
+++ clang-tools-extra/clangd/Hover.cpp
@@ -11,6 +11,7 @@
 #include "AST.h"
 #include "CodeCompletionStrings.h"
 #include "FindTarget.h"
+#include "FormattedString.h"
 #include "Logger.h"
 #include "Selection.h"
 #include "SourceCode.h"
@@ -94,7 +95,7 @@
 }
 
 void printParams(llvm::raw_ostream &OS,
-                        const std::vector<HoverInfo::Param> &Params) {
+                 const std::vector<HoverInfo::Param> &Params) {
   for (size_t I = 0, E = Params.size(); I != E; ++I) {
     if (I)
       OS << ", ";
@@ -199,8 +200,8 @@
 
 // Populates Type, ReturnType, and Parameters for function-like decls.
 void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D,
-                                      const FunctionDecl *FD,
-                                      const PrintingPolicy &Policy) {
+                               const FunctionDecl *FD,
+                               const PrintingPolicy &Policy) {
   HI.Parameters.emplace();
   for (const ParmVarDecl *PVD : FD->parameters()) {
     HI.Parameters->emplace_back();
@@ -225,11 +226,11 @@
     }
   }
 
-  if (const auto* CCD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
+  if (const auto *CCD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
     // Constructor's "return type" is the class type.
     HI.ReturnType = declaredType(CCD->getParent()).getAsString(Policy);
     // Don't provide any type for the constructor itself.
-  } else if (llvm::isa<CXXDestructorDecl>(FD)){
+  } else if (llvm::isa<CXXDestructorDecl>(FD)) {
     HI.ReturnType = "void";
   } else {
     HI.ReturnType = FD->getReturnType().getAsString(Policy);
@@ -242,7 +243,8 @@
   // FIXME: handle variadics.
 }
 
-llvm::Optional<std::string> printExprValue(const Expr *E, const ASTContext &Ctx) {
+llvm::Optional<std::string> printExprValue(const Expr *E,
+                                           const ASTContext &Ctx) {
   Expr::EvalResult Constant;
   // Evaluating [[foo]]() as "&foo" isn't useful, and prevents us walking up
   // to the enclosing call.
@@ -341,7 +343,7 @@
 
 /// Generate a \p Hover object given the type \p T.
 HoverInfo getHoverContents(QualType T, const Decl *D, ASTContext &ASTCtx,
-                                  const SymbolIndex *Index) {
+                           const SymbolIndex *Index) {
   HoverInfo HI;
   llvm::raw_string_ostream OS(HI.Name);
   PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy());
@@ -440,28 +442,29 @@
   return HI;
 }
 
-FormattedString HoverInfo::present() const {
-  FormattedString Output;
+Document HoverInfo::present() const {
+  Document Output;
   if (NamespaceScope) {
-    Output.appendText("Declared in");
+    auto &P = Output.addParagraph();
+    P.appendText("Declared in");
     // Drop trailing "::".
     if (!LocalScope.empty())
-      Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2));
+      P.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2));
     else if (NamespaceScope->empty())
-      Output.appendInlineCode("global namespace");
+      P.appendInlineCode("global namespace");
     else
-      Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
+      P.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
   }
 
   if (!Definition.empty()) {
-    Output.appendCodeBlock(Definition);
+    Output.addParagraph().appendCodeBlock(Definition);
   } else {
     // Builtin types
-    Output.appendCodeBlock(Name);
+    Output.addParagraph().appendCodeBlock(Name);
   }
 
   if (!Documentation.empty())
-    Output.appendText(Documentation);
+    Output.addParagraph().appendText(Documentation);
   return Output;
 }
 
Index: clang-tools-extra/clangd/FormattedString.h
===================================================================
--- clang-tools-extra/clangd/FormattedString.h
+++ clang-tools-extra/clangd/FormattedString.h
@@ -13,6 +13,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -21,30 +22,41 @@
 
 /// A structured string representation that could be converted to markdown or
 /// plaintext upon requrest.
-class FormattedString {
+class RenderableString {
 public:
+  virtual std::string renderAsMarkdown() const = 0;
+  virtual std::string renderAsPlainText() const = 0;
+  virtual std::string renderForTests() const = 0;
+
+  virtual ~RenderableString() = default;
+};
+
+/// Leaf type of the struct representation.
+class Paragraph : public RenderableString {
+public:
+  std::string renderAsMarkdown() const override;
+  std::string renderAsPlainText() const override;
+  std::string renderForTests() const override;
+
   /// 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");
+  Paragraph &appendText(std::string Text);
+
   /// Append an inline block of C++ code. This translates to the ` block in
   /// markdown.
-  void appendInlineCode(std::string Code);
+  Paragraph &appendInlineCode(std::string Code);
 
-  std::string renderAsMarkdown() const;
-  std::string renderAsPlainText() const;
-  std::string renderForTests() const;
+  /// 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.
+  Paragraph &appendCodeBlock(std::string Code, std::string Language = "cpp");
 
 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;
+    enum {
+      PlainText,
+      InlineCode,
+      CodeBlock,
+    } Kind = PlainText;
     std::string Contents;
     /// Language for code block chunks. Ignored for other chunks.
     std::string Language;
@@ -52,6 +64,35 @@
   std::vector<Chunk> Chunks;
 };
 
+/// Container for a set of documents. Each document is prepended with a "- " and
+/// separated by newlines.
+class List : public RenderableString {
+public:
+  std::string renderAsMarkdown() const override;
+  std::string renderAsPlainText() const override;
+  std::string renderForTests() const override;
+
+  class Document &addItem();
+
+private:
+  std::vector<class Document> Documents;
+};
+
+/// Top-level container for structured strings.
+class Document : public RenderableString {
+public:
+  std::string renderAsMarkdown() const override;
+  std::string renderAsPlainText() const override;
+  std::string renderForTests() const override;
+
+  Paragraph &addParagraph();
+
+  List &addList();
+
+private:
+  std::vector<std::unique_ptr<RenderableString>> Children;
+};
+
 } // namespace clangd
 } // namespace clang
 
Index: clang-tools-extra/clangd/FormattedString.cpp
===================================================================
--- clang-tools-extra/clangd/FormattedString.cpp
+++ clang-tools-extra/clangd/FormattedString.cpp
@@ -7,11 +7,13 @@
 //===----------------------------------------------------------------------===//
 #include "FormattedString.h"
 #include "clang/Basic/CharInfo.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormatVariadic.h"
 #include <cstddef>
 #include <string>
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -88,106 +90,86 @@
 
 } // namespace
 
-void FormattedString::appendText(std::string Text) {
-  Chunk C;
-  C.Kind = ChunkKind::PlainText;
-  C.Contents = Text;
-  Chunks.push_back(C);
-}
-
-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) {
-  Chunk C;
-  C.Kind = ChunkKind::InlineCodeBlock;
-  C.Contents = std::move(Code);
-  Chunks.push_back(std::move(C));
-}
-
-std::string FormattedString::renderAsMarkdown() const {
+std::string Paragraph::renderAsMarkdown() const {
   std::string R;
-  auto EnsureWhitespace = [&R]() {
+  bool WasBlock = false;
+  auto EnsureWhitespace = [&]() {
     // Adds a space for nicer rendering.
     if (!R.empty() && !isWhitespace(R.back()))
-      R += " ";
+      R += WasBlock ? '\n' : ' ';
   };
-  for (const auto &C : Chunks) {
+  for (auto &C : Chunks) {
+    llvm::StringRef TrimmedContents = llvm::StringRef(C.Contents).trim();
     switch (C.Kind) {
-    case ChunkKind::PlainText:
-      if (!C.Contents.empty() && !isWhitespace(C.Contents.front()))
-        EnsureWhitespace();
-      R += renderText(C.Contents);
-      continue;
-    case ChunkKind::InlineCodeBlock:
+    case Chunk::PlainText:
       EnsureWhitespace();
-      R += renderInlineBlock(C.Contents);
-      continue;
-    case ChunkKind::CodeBlock:
-      if (!R.empty() && !llvm::StringRef(R).endswith("\n"))
-        R += "\n";
+      R += renderText(TrimmedContents);
+      break;
+    case Chunk::InlineCode:
+      EnsureWhitespace();
+      R += renderInlineBlock(TrimmedContents);
+      break;
+    case Chunk::CodeBlock:
+      // Codeblocks must start on a newline.
+      if (!R.empty() && R.back() != '\n')
+        R += '\n';
+      // Note that codeblocks preserve whitespaces at the beginning/end of the
+      // string.
       R += renderCodeBlock(C.Contents, C.Language);
-      R += "\n";
-      continue;
+      break;
     }
-    llvm_unreachable("unhanlded ChunkKind");
+    WasBlock = C.Kind == Chunk::CodeBlock;
   }
   return R;
 }
 
-std::string FormattedString::renderAsPlainText() const {
+std::string Paragraph::renderAsPlainText() const {
   std::string R;
-  auto EnsureWhitespace = [&]() {
-    if (R.empty() || isWhitespace(R.back()))
-      return;
-    R += " ";
+  auto EnsureWhitespace = [&R]() {
+    // Adds a space for nicer rendering.
+    if (!R.empty() && !isWhitespace(R.back()))
+      R += " ";
   };
-  Optional<bool> LastWasBlock;
-  for (const auto &C : Chunks) {
-    bool IsBlock = C.Kind == ChunkKind::CodeBlock;
-    if (LastWasBlock.hasValue() && (IsBlock || *LastWasBlock))
-      R += "\n\n";
-    LastWasBlock = IsBlock;
-
+  for (auto &C : Chunks) {
+    llvm::StringRef TrimmedContents = llvm::StringRef(C.Contents).trim();
     switch (C.Kind) {
-    case ChunkKind::PlainText:
+    case Chunk::PlainText:
       EnsureWhitespace();
-      R += C.Contents;
+      R += TrimmedContents;
       break;
-    case ChunkKind::InlineCodeBlock:
+    case Chunk::InlineCode:
       EnsureWhitespace();
-      R += C.Contents;
+      R += TrimmedContents;
       break;
-    case ChunkKind::CodeBlock:
-      R += C.Contents;
+    case Chunk::CodeBlock:
+      // Make sure we have an empty line before a code block.
+      if (!R.empty() && !llvm::StringRef(R).endswith("\n\n")) {
+        if (R.back() != '\n')
+          R += "\n";
+        R += "\n";
+      }
+      R += TrimmedContents.str() + "\n\n";
       break;
     }
-    // Trim trailing whitespace in chunk.
-    while (!R.empty() && isWhitespace(R.back()))
-      R.pop_back();
   }
+  // Trim trailing whitespace in chunk.
+  while (!R.empty() && isWhitespace(R.back()))
+    R.pop_back();
   return R;
 }
 
-std::string FormattedString::renderForTests() const {
+std::string Paragraph::renderForTests() const {
   std::string R;
   for (const auto &C : Chunks) {
     switch (C.Kind) {
-    case ChunkKind::PlainText:
+    case Chunk::PlainText:
       R += "text[" + C.Contents + "]";
       break;
-    case ChunkKind::InlineCodeBlock:
+    case Chunk::InlineCode:
       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);
+    case Chunk::CodeBlock:
+      R += llvm::formatv("codeblock({0}) [{1}]", C.Language, C.Contents);
       break;
     }
   }
@@ -195,5 +177,88 @@
     R.pop_back();
   return R;
 }
+
+std::string List::renderAsMarkdown() const {
+  std::vector<std::string> Items;
+  for (auto &D : Documents) {
+    Items.emplace_back("- " + D.renderAsMarkdown());
+  }
+  return llvm::join(Items, "\n");
+}
+
+std::string List::renderAsPlainText() const {
+  std::vector<std::string> Items;
+  for (auto &D : Documents)
+    Items.emplace_back("- " + D.renderAsPlainText());
+  return llvm::join(Items, "\n");
+}
+
+std::string List::renderForTests() const {
+  std::vector<std::string> Items;
+  for (auto &D : Documents)
+    Items.emplace_back(D.renderForTests());
+  return llvm::formatv("List[{0}]", llvm::join(Items, ","));
+}
+
+std::string Document::renderAsMarkdown() const {
+  std::vector<std::string> Items;
+  for (auto &C : Children)
+    Items.emplace_back(C->renderAsMarkdown());
+  return llvm::join(Items, "\n\n");
+}
+
+std::string Document::renderAsPlainText() const {
+  std::vector<std::string> Items;
+  for (auto &C : Children)
+    Items.emplace_back(C->renderAsPlainText());
+  return llvm::join(Items, "\n\n");
+}
+
+std::string Document::renderForTests() const {
+  std::vector<std::string> Items;
+  for (auto &C : Children)
+    Items.emplace_back(C->renderForTests());
+  return llvm::formatv("Document[\n{0}\n]\n", llvm::join(Items, ",\n"));
+}
+
+Paragraph &Paragraph::appendText(std::string Text) {
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Text);
+  C.Kind = Chunk::PlainText;
+  return *this;
+}
+
+Paragraph &Paragraph::appendInlineCode(std::string Code) {
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Code);
+  C.Kind = Chunk::InlineCode;
+  return *this;
+}
+
+Paragraph &Paragraph::appendCodeBlock(std::string Code, std::string Language) {
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Code);
+  C.Language = std::move(Language);
+  C.Kind = Chunk::CodeBlock;
+  return *this;
+}
+
+class Document &List::addItem() {
+  Documents.emplace_back();
+  return Documents.back();
+}
+
+Paragraph &Document::addParagraph() {
+  Children.emplace_back(std::make_unique<Paragraph>());
+  return *static_cast<Paragraph *>(Children.back().get());
+}
+
+List &Document::addList() {
+  Children.emplace_back(std::make_unique<List>());
+  return *static_cast<List *>(Children.back().get());
+}
 } // namespace clangd
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to