diff --git a/clang-tools-extra/clangd/FormattedString.h b/clang-tools-extra/clangd/FormattedString.h --- a/clang-tools-extra/clangd/FormattedString.h +++ b/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 #include #include @@ -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 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 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> Children; +}; + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FormattedString.cpp b/clang-tools-extra/clangd/FormattedString.cpp --- a/clang-tools-extra/clangd/FormattedString.cpp +++ b/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 #include +#include 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 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 Items; + for (auto &D : Documents) { + Items.emplace_back("- " + D.renderAsMarkdown()); + } + return llvm::join(Items, "\n"); +} + +std::string List::renderAsPlainText() const { + std::vector Items; + for (auto &D : Documents) + Items.emplace_back("- " + D.renderAsPlainText()); + return llvm::join(Items, "\n"); +} + +std::string List::renderForTests() const { + std::vector 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 Items; + for (auto &C : Children) + Items.emplace_back(C->renderAsMarkdown()); + return llvm::join(Items, "\n\n"); +} + +std::string Document::renderAsPlainText() const { + std::vector Items; + for (auto &C : Children) + Items.emplace_back(C->renderAsPlainText()); + return llvm::join(Items, "\n\n"); +} + +std::string Document::renderForTests() const { + std::vector 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()); + return *static_cast(Children.back().get()); +} + +List &Document::addList() { + Children.emplace_back(std::make_unique()); + return *static_cast(Children.back().get()); +} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h --- a/clang-tools-extra/clangd/Hover.h +++ b/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 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 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, diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- 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" @@ -94,7 +95,7 @@ } void printParams(llvm::raw_ostream &OS, - const std::vector &Params) { + const std::vector &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(FD)) { + if (const auto *CCD = llvm::dyn_cast(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(FD)){ + } else if (llvm::isa(FD)) { HI.ReturnType = "void"; } else { HI.ReturnType = FD->getReturnType().getAsString(Policy); @@ -242,7 +243,8 @@ // FIXME: handle variadics. } -llvm::Optional printExprValue(const Expr *E, const ASTContext &Ctx) { +llvm::Optional 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; } diff --git a/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp b/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp --- a/clang-tools-extra/clangd/unittests/FormattedStringTests.cpp +++ b/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