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 @@ -14,6 +14,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -86,6 +87,9 @@ /// 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"); + /// Heading is a special type of paragraph that will be prepended with \p + /// Level many '#'s in markdown. + Paragraph &addHeading(size_t Level); BulletList &addBulletList(); 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 @@ -164,6 +164,19 @@ } return IndentedR; } + +class Heading : public Paragraph { +public: + Heading(size_t Level) : Level(Level) {} + void renderMarkdown(llvm::raw_ostream &OS) const override { + OS << std::string(Level, '#') << ' '; + Paragraph::renderMarkdown(OS); + } + +private: + size_t Level; +}; + } // namespace std::string Block::asMarkdown() const { @@ -278,6 +291,12 @@ Children.emplace_back(std::make_unique()); return *static_cast(Children.back().get()); } + +Paragraph &Document::addHeading(size_t Level) { + assert(Level > 0); + Children.emplace_back(std::make_unique(Level)); + return *static_cast(Children.back().get()); +} } // namespace markup } // namespace clangd } // namespace clang 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 @@ -469,7 +469,10 @@ // class `X` // // function `foo` → `int` - markup::Paragraph &Header = Output.addParagraph(); + // Note that we are making use of a level-3 heading because VSCode renders + // level 1 and 2 headers in a huge font, see + // https://github.com/microsoft/vscode/issues/88417 for details. + markup::Paragraph &Header = Output.addHeading(3); Header.appendText(index::getSymbolKindString(Kind)); assert(!Name.empty() && "hover triggered on a nameless symbol"); Header.appendCode(Name); 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 @@ -145,6 +145,15 @@ EXPECT_EQ(D.asPlainText(), "foo\n\nbar"); } +TEST(Document, Heading) { + Document D; + D.addHeading(1).appendText("foo"); + D.addHeading(2).appendText("bar"); + D.addParagraph().appendText("baz"); + EXPECT_EQ(D.asMarkdown(), "# foo \n## bar \nbaz"); + EXPECT_EQ(D.asPlainText(), "foo\nbar\nbaz"); +} + TEST(CodeBlock, Render) { Document D; // Code blocks preserves any extra spaces. 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 @@ -1631,6 +1631,7 @@ } } } + TEST(Hover, Present) { struct { const std::function Builder; @@ -1720,6 +1721,17 @@ } } +// This is a separate test as headings don't create any differences in plaintext +// mode. +TEST(Hover, PresentHeadings) { + HoverInfo HI; + HI.Kind = index::SymbolKind::Variable; + HI.Name = "foo"; + HI.Type = "type"; + + EXPECT_EQ(HI.present().asMarkdown(), "### variable `foo` \\: `type`"); +} + } // namespace } // namespace clangd } // namespace clang