kadircet created this revision. kadircet added a reviewer: sammccall. Herald added subscribers: cfe-commits, usaxena95, arphaman, jkorous, MaskRay, ilya-biryukov. Herald added a project: clang. kadircet added a comment. kadircet added a parent revision: D70911: [clangd] Switch Hover.All to structured tests.
Note that I am planning to add more tests, sending out for review to get some initial feedback on the design. Clangd currently has a really basic formatted string generation framework and it is not enough to generate some hover responses we want to. This patch improves that infrastructure by adding more types of nodes and renderings. While generating markdown, spesifications in https://github.github.com/gfm/ was followed. Repository: rG LLVM Github Monorepo 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,60 @@ /// 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) { + Chunks.emplace_back(); + Chunk &C = Chunks.back(); + C.Contents = std::move(Text); + C.Kind = Chunk::PlainText; + return *this; + } + /// Append an inline block of C++ code. This translates to the ` block in /// markdown. - void appendInlineCode(std::string Code); + Paragraph &appendInlineCode(std::string Code) { + Chunks.emplace_back(); + Chunk &C = Chunks.back(); + C.Contents = std::move(Code); + C.Kind = Chunk::InlineCode; + return *this; + } - 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") { + Chunks.emplace_back(); + Chunk &C = Chunks.back(); + C.Contents = std::move(Code); + C.Language = std::move(Language); + C.Kind = Chunk::CodeBlock; + return *this; + } 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 +83,44 @@ 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() { + Documents.emplace_back(); + return Documents.back(); + } + +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() { + Children.emplace_back(std::make_unique<Paragraph>()); + return *static_cast<Paragraph *>(Children.back().get()); + } + + List &addList() { + Children.emplace_back(std::make_unique<List>()); + return *static_cast<List *>(Children.back().get()); + } + +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,48 @@ 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")); +} } // namespace clangd } // namespace clang
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits