[clang-tools-extra] 597c6b6 - [clangd] Introduce paragraph, the first part of new rendering structs

Kadir Cetinkaya via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 13 00:59:04 PST 2019


Author: Kadir Cetinkaya
Date: 2019-12-13T09:58:55+01:00
New Revision: 597c6b65552a777a40f2afed07c543f6789318b1

URL: https://github.com/llvm/llvm-project/commit/597c6b65552a777a40f2afed07c543f6789318b1
DIFF: https://github.com/llvm/llvm-project/commit/597c6b65552a777a40f2afed07c543f6789318b1.diff

LOG: [clangd] Introduce paragraph, the first part of new rendering structs

Summary:
Initial patch for new rendering structs in clangd.

Splitting implementation into smaller chunks, for a full view of the API see D71063.

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

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

Reviewers: sammccall

Added: 
    

Modified: 
    clang-tools-extra/clangd/ClangdLSPServer.cpp
    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

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index cb1f02ff68b9..69b4308a1c9e 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1095,10 +1095,10 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
                       R.range = (*H)->SymRange;
                       switch (HoverContentFormat) {
                       case MarkupKind::PlainText:
-                        R.contents.value = (*H)->present().renderAsPlainText();
+                        R.contents.value = (*H)->present().asPlainText();
                         return Reply(std::move(R));
                       case MarkupKind::Markdown:
-                        R.contents.value = (*H)->present().renderAsMarkdown();
+                        R.contents.value = (*H)->present().asMarkdown();
                         return Reply(std::move(R));
                       };
                       llvm_unreachable("unhandled MarkupKind");

diff  --git a/clang-tools-extra/clangd/FormattedString.cpp b/clang-tools-extra/clangd/FormattedString.cpp
index 27fd37b9ae62..8bb17f38012b 100644
--- a/clang-tools-extra/clangd/FormattedString.cpp
+++ b/clang-tools-extra/clangd/FormattedString.cpp
@@ -7,19 +7,27 @@
 //===----------------------------------------------------------------------===//
 #include "FormattedString.h"
 #include "clang/Basic/CharInfo.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/raw_ostream.h"
 #include <cstddef>
+#include <memory>
 #include <string>
+#include <vector>
 
 namespace clang {
 namespace clangd {
+namespace markup {
 
 namespace {
 /// Escape a markdown text block. Ensures the punctuation will not introduce
 /// any of the markdown constructs.
-static std::string renderText(llvm::StringRef Input) {
+std::string renderText(llvm::StringRef Input) {
   // Escaping ASCII punctiation ensures we can't start a markdown construct.
   constexpr llvm::StringLiteral Punctuation =
       R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
@@ -40,7 +48,7 @@ static std::string renderText(llvm::StringRef Input) {
 
 /// 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 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();) {
@@ -63,11 +71,11 @@ static std::string renderInlineBlock(llvm::StringRef Input) {
     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) {
+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;
@@ -86,114 +94,123 @@ static std::string renderCodeBlock(llvm::StringRef Input,
   return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker;
 }
 
-} // namespace
-
-void FormattedString::appendText(std::string Text) {
-  Chunk C;
-  C.Kind = ChunkKind::PlainText;
-  C.Contents = Text;
-  Chunks.push_back(C);
+// Trims the input and concatanates whitespace blocks into a single ` `.
+std::string canonicalizeSpaces(std::string Input) {
+  // Goes over the string and preserves only a single ` ` for any whitespace
+  // chunks, the rest is moved to the end of the string and dropped in the end.
+  auto WritePtr = Input.begin();
+  llvm::SmallVector<llvm::StringRef, 4> Words;
+  llvm::SplitString(Input, Words);
+  if (Words.empty())
+    return "";
+  // Go over each word and and add it to the string.
+  for (llvm::StringRef Word : Words) {
+    llvm::for_each(Word, [&WritePtr](const char C) { *WritePtr++ = C; });
+    // Separate from next block.
+    *WritePtr++ = ' ';
+  }
+  // Get rid of extra spaces, -1 is for the trailing space introduced with last
+  // word.
+  Input.resize(WritePtr - Input.begin() - 1);
+  return Input;
 }
 
-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));
+std::string renderBlocks(llvm::ArrayRef<std::unique_ptr<Block>> Children,
+                         void (Block::*RenderFunc)(llvm::raw_ostream &) const) {
+  std::string R;
+  llvm::raw_string_ostream OS(R);
+  for (auto &C : Children)
+    ((*C).*RenderFunc)(OS);
+  return llvm::StringRef(OS.str()).trim().str();
 }
 
-void FormattedString::appendInlineCode(std::string Code) {
-  Chunk C;
-  C.Kind = ChunkKind::InlineCodeBlock;
-  C.Contents = std::move(Code);
-  Chunks.push_back(std::move(C));
-}
+// Puts a vertical space between blocks inside a document.
+class Spacer : public Block {
+public:
+  void renderMarkdown(llvm::raw_ostream &OS) const override { OS << '\n'; }
+  void renderPlainText(llvm::raw_ostream &OS) const override { OS << '\n'; }
+};
+
+} // namespace
 
-std::string FormattedString::renderAsMarkdown() const {
+std::string Block::asMarkdown() const {
   std::string R;
-  auto EnsureWhitespace = [&R]() {
-    // Adds a space for nicer rendering.
-    if (!R.empty() && !isWhitespace(R.back()))
-      R += " ";
-  };
-  for (const auto &C : Chunks) {
-    switch (C.Kind) {
-    case ChunkKind::PlainText:
-      if (!C.Contents.empty() && !isWhitespace(C.Contents.front()))
-        EnsureWhitespace();
-      R += renderText(C.Contents);
-      continue;
-    case ChunkKind::InlineCodeBlock:
-      EnsureWhitespace();
-      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;
+  llvm::raw_string_ostream OS(R);
+  renderMarkdown(OS);
+  return llvm::StringRef(OS.str()).trim().str();
 }
 
-std::string FormattedString::renderAsPlainText() const {
+std::string Block::asPlainText() const {
   std::string R;
-  auto EnsureWhitespace = [&]() {
-    if (R.empty() || isWhitespace(R.back()))
-      return;
-    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;
+  llvm::raw_string_ostream OS(R);
+  renderPlainText(OS);
+  return llvm::StringRef(OS.str()).trim().str();
+}
 
+void Paragraph::renderMarkdown(llvm::raw_ostream &OS) const {
+  llvm::StringRef Sep = "";
+  for (auto &C : Chunks) {
+    OS << Sep;
     switch (C.Kind) {
-    case ChunkKind::PlainText:
-      EnsureWhitespace();
-      R += C.Contents;
+    case Chunk::PlainText:
+      OS << renderText(C.Contents);
       break;
-    case ChunkKind::InlineCodeBlock:
-      EnsureWhitespace();
-      R += C.Contents;
-      break;
-    case ChunkKind::CodeBlock:
-      R += C.Contents;
+    case Chunk::InlineCode:
+      OS << renderInlineBlock(C.Contents);
       break;
     }
-    // Trim trailing whitespace in chunk.
-    while (!R.empty() && isWhitespace(R.back()))
-      R.pop_back();
+    Sep = " ";
   }
-  return R;
+  // Paragraphs are translated into markdown lines, not markdown paragraphs.
+  // Therefore it only has a single linebreak afterwards.
+  OS << '\n';
 }
 
-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;
-    }
+void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
+  llvm::StringRef Sep = "";
+  for (auto &C : Chunks) {
+    OS << Sep << C.Contents;
+    Sep = " ";
   }
-  while (!R.empty() && isWhitespace(R.back()))
-    R.pop_back();
-  return R;
+  OS << '\n';
+}
+
+Paragraph &Paragraph::appendText(std::string Text) {
+  Text = canonicalizeSpaces(std::move(Text));
+  if (Text.empty())
+    return *this;
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Text);
+  C.Kind = Chunk::PlainText;
+  return *this;
+}
+
+Paragraph &Paragraph::appendCode(std::string Code) {
+  Code = canonicalizeSpaces(std::move(Code));
+  if (Code.empty())
+    return *this;
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Code);
+  C.Kind = Chunk::InlineCode;
+  return *this;
+}
+
+Paragraph &Document::addParagraph() {
+  Children.push_back(std::make_unique<Paragraph>());
+  return *static_cast<Paragraph *>(Children.back().get());
+}
+
+void Document::addSpacer() { Children.push_back(std::make_unique<Spacer>()); }
+
+std::string Document::asMarkdown() const {
+  return renderBlocks(Children, &Block::renderMarkdown);
+}
+
+std::string Document::asPlainText() const {
+  return renderBlocks(Children, &Block::renderPlainText);
 }
+} // namespace markup
 } // namespace clangd
 } // namespace clang

diff  --git a/clang-tools-extra/clangd/FormattedString.h b/clang-tools-extra/clangd/FormattedString.h
index 10313a4408cf..02753950f0cf 100644
--- a/clang-tools-extra/clangd/FormattedString.h
+++ b/clang-tools-extra/clangd/FormattedString.h
@@ -13,38 +13,48 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
 
+#include "llvm/Support/raw_ostream.h"
+#include <memory>
 #include <string>
 #include <vector>
 
 namespace clang {
 namespace clangd {
+namespace markup {
 
-/// A structured string representation that could be converted to markdown or
-/// plaintext upon requrest.
-class FormattedString {
+/// Holds text and knows how to lay it out. Multiple blocks can be grouped to
+/// form a document. Blocks include their own trailing newlines, container
+/// should trim them if need be.
+class Block {
 public:
+  virtual void renderMarkdown(llvm::raw_ostream &OS) const = 0;
+  virtual void renderPlainText(llvm::raw_ostream &OS) const = 0;
+  std::string asMarkdown() const;
+  std::string asPlainText() const;
+
+  virtual ~Block() = default;
+};
+
+/// Represents parts of the markup that can contain strings, like inline code,
+/// code block or plain text.
+/// One must introduce 
diff erent paragraphs to create separate blocks.
+class Paragraph : public Block {
+public:
+  void renderMarkdown(llvm::raw_ostream &OS) const override;
+  void renderPlainText(llvm::raw_ostream &OS) 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");
-  /// Append an inline block of C++ code. This translates to the ` block in
-  /// markdown.
-  void appendInlineCode(std::string Code);
-
-  std::string renderAsMarkdown() const;
-  std::string renderAsPlainText() const;
-  std::string renderForTests() const;
+  Paragraph &appendText(std::string Text);
+
+  /// Append inline code, this translates to the ` block in markdown.
+  Paragraph &appendCode(std::string Code);
 
 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,
+    } Kind = PlainText;
     std::string Contents;
     /// Language for code block chunks. Ignored for other chunks.
     std::string Language;
@@ -52,6 +62,23 @@ class FormattedString {
   std::vector<Chunk> Chunks;
 };
 
+/// A format-agnostic representation for structured text. Allows rendering into
+/// markdown and plaintext.
+class Document {
+public:
+  /// Adds a semantical block that will be separate from others.
+  Paragraph &addParagraph();
+  /// Inserts a vertical space into the document.
+  void addSpacer();
+
+  std::string asMarkdown() const;
+  std::string asPlainText() const;
+
+private:
+  std::vector<std::unique_ptr<Block>> Children;
+};
+
+} // namespace markup
 } // namespace clangd
 } // namespace clang
 

diff  --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index d89dc298ed85..d6b270c826d9 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/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"
@@ -441,28 +442,30 @@ llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
   return HI;
 }
 
-FormattedString HoverInfo::present() const {
-  FormattedString Output;
+markup::Document HoverInfo::present() const {
+  markup::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.appendCode(llvm::StringRef(LocalScope).drop_back(2));
     else if (NamespaceScope->empty())
-      Output.appendInlineCode("global namespace");
+      P.appendCode("global namespace");
     else
-      Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
+      P.appendCode(llvm::StringRef(*NamespaceScope).drop_back(2));
   }
 
+  Output.addSpacer();
   if (!Definition.empty()) {
-    Output.appendCodeBlock(Definition);
+    Output.addParagraph().appendCode(Definition);
   } else {
     // Builtin types
-    Output.appendCodeBlock(Name);
+    Output.addParagraph().appendCode(Name);
   }
 
   if (!Documentation.empty())
-    Output.appendText(Documentation);
+    Output.addParagraph().appendText(Documentation);
   return Output;
 }
 

diff  --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index f42e7f3a7b54..40a10ff6a63f 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -72,7 +72,7 @@ struct HoverInfo {
   llvm::Optional<std::string> Value;
 
   /// Produce a user-readable information.
-  FormattedString present() const;
+  markup::Document present() const;
 };
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
 inline bool operator==(const HoverInfo::Param &LHS,

diff  --git a/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp b/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
index e8077563df1a..39f5c64a3817 100644
--- a/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp
@@ -8,192 +8,109 @@
 #include "FormattedString.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/StringRef.h"
-
+#include "llvm/Support/raw_ostream.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace clang {
 namespace clangd {
+namespace markup {
 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(FormattedString, CodeBlocks) {
-  FormattedString S;
-  S.appendCodeBlock("foobar");
-  S.appendCodeBlock("bazqux", "javascript");
-  S.appendText("after");
-
-  std::string ExpectedText = R"(foobar
-
-bazqux
-
-after)";
-  EXPECT_EQ(S.renderAsPlainText(), ExpectedText);
-  std::string ExpectedMarkdown = 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");
-}
-
-TEST(FormattedString, Escaping) {
+TEST(Render, Escaping) {
   // Check some ASCII punctuation
-  FormattedString S;
-  S.appendText("*!`");
-  EXPECT_EQ(S.renderAsMarkdown(), "\\*\\!\\`");
+  Paragraph P;
+  P.appendText("*!`");
+  EXPECT_EQ(P.asMarkdown(), "\\*\\!\\`");
 
   // 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.asMarkdown(), 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");
+  P = Paragraph();
+  P.appendCode("* foo !+ bar * baz");
+  EXPECT_EQ(P.asMarkdown(), "`* foo !+ bar * baz`");
 
   // 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");
+  P = Paragraph();
+  P.appendCode("foo`bar`baz");
+  EXPECT_EQ(P.asMarkdown(), "`foo``bar``baz`");
 
   // 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");
+  P = Paragraph();
+  P.appendCode("`foo");
+  EXPECT_EQ(P.asMarkdown(), "` ``foo `");
+  P = Paragraph();
+  P.appendCode("foo`");
+  EXPECT_EQ(P.asMarkdown(), "` foo`` `");
+  P = Paragraph();
+  P.appendCode("`foo`");
+  EXPECT_EQ(P.asMarkdown(), "` ``foo`` `");
 }
 
-TEST(FormattedString, MarkdownWhitespace) {
-  // Whitespace should be added as separators between blocks.
-  FormattedString S;
-  S.appendText("foo");
-  S.appendText("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo bar");
+TEST(Paragraph, SeparationOfChunks) {
+  // This test keeps appending contents to a single Paragraph and checks
+  // expected accumulated contents after each one.
+  // Purpose is to check for separation between 
diff erent chunks.
+  Paragraph P;
 
-  S = FormattedString();
-  S.appendInlineCode("foo");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "`foo` `bar`");
+  P.appendText("after");
+  EXPECT_EQ(P.asMarkdown(), "after");
+  EXPECT_EQ(P.asPlainText(), "after");
 
-  // However, we don't want to add any extra whitespace.
-  S = FormattedString();
-  S.appendText("foo ");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo `bar`");
+  P.appendCode("foobar");
+  EXPECT_EQ(P.asMarkdown(), "after `foobar`");
+  EXPECT_EQ(P.asPlainText(), "after foobar");
 
-  S = FormattedString();
-  S.appendText("foo\n");
-  S.appendInlineCode("bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo\n`bar`");
+  P.appendText("bat");
+  EXPECT_EQ(P.asMarkdown(), "after `foobar` bat");
+  EXPECT_EQ(P.asPlainText(), "after foobar bat");
+}
 
-  S = FormattedString();
-  S.appendInlineCode("foo");
-  S.appendText(" bar");
-  EXPECT_EQ(S.renderAsMarkdown(), "`foo` bar");
+TEST(Paragraph, ExtraSpaces) {
+  // Make sure spaces inside chunks are dropped.
+  Paragraph P;
+  P.appendText("foo\n   \t   baz");
+  P.appendCode(" bar\n");
+  EXPECT_EQ(P.asMarkdown(), R"md(foo baz `bar`)md");
+  EXPECT_EQ(P.asPlainText(), R"pt(foo baz bar)pt");
+}
 
-  S = FormattedString();
-  S.appendText("foo");
-  S.appendCodeBlock("bar");
-  S.appendText("baz");
-  EXPECT_EQ(S.renderAsMarkdown(), "foo\n```cpp\nbar\n```\nbaz");
+TEST(Paragraph, NewLines) {
+  // New lines before and after chunks are dropped.
+  Paragraph P;
+  P.appendText(" \n foo\nbar\n ");
+  P.appendCode(" \n foo\nbar \n ");
+  EXPECT_EQ(P.asMarkdown(), R"md(foo bar `foo bar`)md");
+  EXPECT_EQ(P.asPlainText(), R"pt(foo bar foo bar)pt");
 }
 
+TEST(Document, Separators) {
+  Document D;
+  D.addParagraph().appendText("foo");
+  D.addParagraph().appendText("bar");
+  EXPECT_EQ(D.asMarkdown(), "foo\nbar");
+  EXPECT_EQ(D.asPlainText(), "foo\nbar");
+}
+
+TEST(Document, Spacer) {
+  Document D;
+  D.addParagraph().appendText("foo");
+  D.addSpacer();
+  D.addParagraph().appendText("bar");
+  EXPECT_EQ(D.asMarkdown(), "foo\n\nbar");
+  EXPECT_EQ(D.asPlainText(), "foo\n\nbar");
+}
 
 } // namespace
+} // namespace markup
 } // namespace clangd
 } // namespace clang


        


More information about the cfe-commits mailing list