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 @@ -70,6 +70,9 @@ Paragraph &addParagraph(); /// Inserts a vertical space into the document. void addSpacer(); + /// Adds a block of code. This translates to a ``` block in markdown. In plain + /// text representation, the code block will be surrounded by newlines. + void addCodeBlock(std::string Code, std::string Language = "cpp"); std::string asMarkdown() const; std::string asPlainText() const; 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 @@ -72,10 +72,10 @@ 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. -std::string renderCodeBlock(llvm::StringRef Input, llvm::StringRef Language) { +/// Get marker required for \p Input to represent a markdown codeblock. It +/// consists of at least 3 backticks(`). Although markdown also allows to use +/// tilde(~) for code blocks, they are never used. +std::string getMarkerForCodeBlock(llvm::StringRef Input) { // Count the maximum number of consecutive backticks in \p Input. We need to // start and end the code block with more. unsigned MaxBackticks = 0; @@ -90,8 +90,7 @@ } MaxBackticks = std::max(Backticks, MaxBackticks); // Use the corresponding number of backticks to start and end a code block. - std::string BlockMarker(/*Repeat=*/std::max(3u, MaxBackticks + 1), '`'); - return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker; + return std::string(/*Repeat=*/std::max(3u, MaxBackticks + 1), '`'); } // Trims the input and concatanates whitespace blocks into a single ` `. @@ -131,6 +130,26 @@ void renderPlainText(llvm::raw_ostream &OS) const override { OS << '\n'; } }; +class CodeBlock : public Block { +public: + void renderMarkdown(llvm::raw_ostream &OS) const override { + std::string Marker = getMarkerForCodeBlock(Contents); + // No need to pad from previous blocks, as they should end with a new line. + OS << Marker << Language << '\n' << Contents << '\n' << Marker << '\n'; + } + + void renderPlainText(llvm::raw_ostream &OS) const override { + // In plaintext we want one empty line before and after codeblocks. + OS << '\n' << Contents << "\n\n"; + } + + CodeBlock(std::string Contents, std::string Language) + : Contents(std::move(Contents)), Language(std::move(Language)) {} + +private: + std::string Contents; + std::string Language; +}; } // namespace std::string Block::asMarkdown() const { @@ -204,6 +223,11 @@ void Document::addSpacer() { Children.push_back(std::make_unique()); } +void Document::addCodeBlock(std::string Code, std::string Language) { + Children.emplace_back( + std::make_unique(std::move(Code), std::move(Language))); +} + std::string Document::asMarkdown() const { return renderBlocks(Children, &Block::renderMarkdown); } 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 @@ -95,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 << ", "; @@ -456,12 +456,11 @@ P.appendCode(llvm::StringRef(*NamespaceScope).drop_back(2)); } - Output.addSpacer(); if (!Definition.empty()) { - Output.addParagraph().appendCode(Definition); + Output.addCodeBlock(Definition); } else { // Builtin types - Output.addParagraph().appendCode(Name); + Output.addCodeBlock(Name); } if (!Documentation.empty()) 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 @@ -54,6 +54,28 @@ P = Paragraph(); P.appendCode("`foo`"); EXPECT_EQ(P.asMarkdown(), "` ``foo`` `"); + + // Code blocks might need more than 3 backticks. + Document D; + D.addCodeBlock("foobarbaz `\nqux"); + EXPECT_EQ(D.asMarkdown(), "```cpp\n" + "foobarbaz `\nqux\n" + "```"); + D = Document(); + D.addCodeBlock("foobarbaz ``\nqux"); + EXPECT_THAT(D.asMarkdown(), "```cpp\n" + "foobarbaz ``\nqux\n" + "```"); + D = Document(); + D.addCodeBlock("foobarbaz ```\nqux"); + EXPECT_EQ(D.asMarkdown(), "````cpp\n" + "foobarbaz ```\nqux\n" + "````"); + D = Document(); + D.addCodeBlock("foobarbaz ` `` ``` ```` `\nqux"); + EXPECT_EQ(D.asMarkdown(), "`````cpp\n" + "foobarbaz ` `` ``` ```` `\nqux\n" + "`````"); } TEST(Paragraph, SeparationOfChunks) { @@ -96,9 +118,18 @@ TEST(Document, Separators) { Document D; D.addParagraph().appendText("foo"); + D.addCodeBlock("test"); D.addParagraph().appendText("bar"); - EXPECT_EQ(D.asMarkdown(), "foo\nbar"); - EXPECT_EQ(D.asPlainText(), "foo\nbar"); + EXPECT_EQ(D.asMarkdown(), R"md(foo +```cpp +test +``` +bar)md"); + EXPECT_EQ(D.asPlainText(), R"pt(foo + +test + +bar)pt"); } TEST(Document, Spacer) { @@ -110,6 +141,38 @@ EXPECT_EQ(D.asPlainText(), "foo\n\nbar"); } +TEST(CodeBlock, Render) { + Document D; + // Code blocks preserves any extra spaces. + D.addCodeBlock("foo\n bar\n baz"); + EXPECT_EQ(D.asMarkdown(), R"md(```cpp +foo + bar + baz +```)md"); + EXPECT_EQ(D.asPlainText(), R"pt(foo + bar + baz)pt"); + D.addCodeBlock("foo"); + EXPECT_EQ(D.asMarkdown(), R"md(```cpp +foo + bar + baz +``` +```cpp +foo +```)md"); + // FIXME: we shouldn't have 2 empty lines in between. A solution might be + // having a `verticalMargin` method for blocks, and let container insert new + // lines according to that before/after blocks. + EXPECT_EQ(D.asPlainText(), R"pt(foo + bar + baz + + +foo)pt"); +} + } // namespace } // namespace markup } // namespace clangd