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 @@ -51,7 +51,8 @@ Paragraph &appendText(llvm::StringRef Text); /// Append inline code, this translates to the ` block in markdown. - Paragraph &appendCode(llvm::StringRef Code); + /// \p Preserve indicates the code span must be apparent even in plaintext. + Paragraph &appendCode(llvm::StringRef Code, bool Preserve = false); private: struct Chunk { @@ -59,6 +60,8 @@ PlainText, InlineCode, } Kind = PlainText; + // Preserve chunk markers in plaintext. + bool Preserve = false; std::string Contents; }; std::vector Chunks; 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 @@ -369,10 +369,24 @@ return std::make_unique(*this); } +/// Choose a marker to delimit `Text` from a prioritized list of options. +/// This is more readable than escaping for plain-text. +llvm::StringRef chooseMarker(llvm::ArrayRef Options, + llvm::StringRef Text) { + // Prefer a delimiter whose characters don't appear in the text. + for (llvm::StringRef S : Options) + if (Text.find_first_of(S) == llvm::StringRef::npos) + return S; + return Options.front(); +} + void Paragraph::renderPlainText(llvm::raw_ostream &OS) const { llvm::StringRef Sep = ""; for (auto &C : Chunks) { - OS << Sep << C.Contents; + llvm::StringRef Marker = ""; + if (C.Preserve && C.Kind == Chunk::InlineCode) + Marker = chooseMarker({"`", "'", "\""}, C.Contents); + OS << Sep << Marker << C.Contents << Marker; Sep = " "; } OS << '\n'; @@ -407,7 +421,7 @@ return *this; } -Paragraph &Paragraph::appendCode(llvm::StringRef Code) { +Paragraph &Paragraph::appendCode(llvm::StringRef Code, bool Preserve) { std::string Norm = canonicalizeSpaces(std::move(Code)); if (Norm.empty()) return *this; @@ -415,6 +429,7 @@ Chunk &C = Chunks.back(); C.Contents = std::move(Norm); C.Kind = Chunk::InlineCode; + C.Preserve = Preserve; return *this; } 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 @@ -877,7 +877,7 @@ case '`': if (auto Range = getBacktickQuoteRange(Line, I)) { Out.appendText(Line.substr(0, I)); - Out.appendCode(Range->trim("`")); + Out.appendCode(Range->trim("`"), /*Preserve=*/true); return parseDocumentationLine(Line.substr(I+Range->size()), Out); } break; 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 @@ -112,8 +112,10 @@ // But we have to escape the backticks. P = Paragraph(); - P.appendCode("foo`bar`baz"); + P.appendCode("foo`bar`baz", /*Preserve=*/true); EXPECT_EQ(P.asMarkdown(), "`foo``bar``baz`"); + // In plain-text, we fall back to different quotes. + EXPECT_EQ(P.asPlainText(), "'foo`bar`baz'"); // Inline code blocks starting or ending with backticks should add spaces. P = Paragraph(); @@ -149,6 +151,17 @@ "`````"); } +TEST(Paragraph, Chunks) { + Paragraph P = Paragraph(); + P.appendText("One "); + P.appendCode("fish"); + P.appendText(", two"); + P.appendCode("fish", /*Preserve=*/true); + + EXPECT_EQ(P.asMarkdown(), "One `fish` , two `fish`"); + EXPECT_EQ(P.asPlainText(), "One fish , two `fish`"); +} + TEST(Paragraph, SeparationOfChunks) { // This test keeps appending contents to a single Paragraph and checks // expected accumulated contents after each one. diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -2022,12 +2022,12 @@ // FIXME: we insert spaces between code and text chunk. "Tests primality of `p`.", "Tests primality of `p` .", - "Tests primality of p .", + "Tests primality of `p` .", }, { "'`' should not occur in `Code`", "'\\`' should not occur in `Code`", - "'`' should not occur in Code", + "'`' should not occur in `Code`", }, { "`not\nparsed`",