[clangd] Parse `foo` in documentation comments and render as code.

Reviewers: kadircet

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D77456
This commit is contained in:
Sam McCall 2020-04-04 09:12:30 +02:00
parent 1ccde53342
commit 30d17d8852
4 changed files with 80 additions and 30 deletions

View File

@ -216,23 +216,10 @@ std::string getMarkerForCodeBlock(llvm::StringRef Input) {
}
// Trims the input and concatenates whitespace blocks into a single ` `.
std::string canonicalizeSpaces(std::string Input) {
// Goes over the string and preserves only a single ` ` for any whitespace
// chunks, the rest is moved to the end of the string and dropped in the end.
auto WritePtr = Input.begin();
std::string canonicalizeSpaces(llvm::StringRef Input) {
llvm::SmallVector<llvm::StringRef, 4> Words;
llvm::SplitString(Input, Words);
if (Words.empty())
return "";
// Go over each word and add it to the string.
for (llvm::StringRef Word : Words) {
if (WritePtr > Input.begin())
*WritePtr++ = ' '; // Separate from previous block.
llvm::for_each(Word, [&WritePtr](const char C) { *WritePtr++ = C; });
}
// Get rid of extra spaces.
Input.resize(WritePtr - Input.begin());
return Input;
return llvm::join(Words, " ");
}
std::string renderBlocks(llvm::ArrayRef<std::unique_ptr<Block>> Children,
@ -398,24 +385,24 @@ void BulletList::renderPlainText(llvm::raw_ostream &OS) const {
}
}
Paragraph &Paragraph::appendText(std::string Text) {
Text = canonicalizeSpaces(std::move(Text));
if (Text.empty())
Paragraph &Paragraph::appendText(llvm::StringRef Text) {
std::string Norm = canonicalizeSpaces(Text);
if (Norm.empty())
return *this;
Chunks.emplace_back();
Chunk &C = Chunks.back();
C.Contents = std::move(Text);
C.Contents = std::move(Norm);
C.Kind = Chunk::PlainText;
return *this;
}
Paragraph &Paragraph::appendCode(std::string Code) {
Code = canonicalizeSpaces(std::move(Code));
if (Code.empty())
Paragraph &Paragraph::appendCode(llvm::StringRef Code) {
std::string Norm = canonicalizeSpaces(std::move(Code));
if (Norm.empty())
return *this;
Chunks.emplace_back();
Chunk &C = Chunks.back();
C.Contents = std::move(Code);
C.Contents = std::move(Norm);
C.Kind = Chunk::InlineCode;
return *this;
}

View File

@ -46,10 +46,10 @@ public:
void renderPlainText(llvm::raw_ostream &OS) const override;
/// Append plain text to the end of the string.
Paragraph &appendText(std::string Text);
Paragraph &appendText(llvm::StringRef Text);
/// Append inline code, this translates to the ` block in markdown.
Paragraph &appendCode(std::string Code);
Paragraph &appendCode(llvm::StringRef Code);
private:
struct Chunk {

View File

@ -772,7 +772,7 @@ markup::Document HoverInfo::present() const {
// https://github.com/microsoft/vscode/issues/88417 for details.
markup::Paragraph &Header = Output.addHeading(3);
if (Kind != index::SymbolKind::Unknown)
Header.appendText(std::string(index::getSymbolKindString(Kind)));
Header.appendText(index::getSymbolKindString(Kind));
assert(!Name.empty() && "hover triggered on a nameless symbol");
Header.appendCode(Name);
@ -809,10 +809,11 @@ markup::Document HoverInfo::present() const {
if (Offset)
Output.addParagraph().appendText(
llvm::formatv("Offset: {0} byte{1}", *Offset, *Offset == 1 ? "" : "s"));
llvm::formatv("Offset: {0} byte{1}", *Offset, *Offset == 1 ? "" : "s")
.str());
if (Size)
Output.addParagraph().appendText(
llvm::formatv("Size: {0} byte{1}", *Size, *Size == 1 ? "" : "s"));
llvm::formatv("Size: {0} byte{1}", *Size, *Size == 1 ? "" : "s").str());
if (!Documentation.empty())
parseDocumentation(Documentation, Output);
@ -838,6 +839,52 @@ markup::Document HoverInfo::present() const {
return Output;
}
// If the backtick at `Offset` starts a probable quoted range, return the range
// (including the quotes).
llvm::Optional<llvm::StringRef> getBacktickQuoteRange(llvm::StringRef Line,
unsigned Offset) {
assert(Line[Offset] == '`');
// The open-quote is usually preceded by whitespace.
llvm::StringRef Prefix = Line.substr(0, Offset);
constexpr llvm::StringLiteral BeforeStartChars = " \t(=";
if (!Prefix.empty() && !BeforeStartChars.contains(Prefix.back()))
return llvm::None;
// The quoted string must be nonempty and usually has no leading/trailing ws.
auto Next = Line.find('`', Offset + 1);
if (Next == llvm::StringRef::npos)
return llvm::None;
llvm::StringRef Contents = Line.slice(Offset + 1, Next);
if (Contents.empty() || isWhitespace(Contents.front()) ||
isWhitespace(Contents.back()))
return llvm::None;
// The close-quote is usually followed by whitespace or punctuation.
llvm::StringRef Suffix = Line.substr(Next + 1);
constexpr llvm::StringLiteral AfterEndChars = " \t)=.,;:";
if (!Suffix.empty() && !AfterEndChars.contains(Suffix.front()))
return llvm::None;
return Line.slice(Offset, Next+1);
}
void parseDocumentationLine(llvm::StringRef Line, markup::Paragraph &Out) {
// Probably this is appendText(Line), but scan for something interesting.
for (unsigned I = 0; I < Line.size(); ++I) {
switch (Line[I]) {
case '`':
if (auto Range = getBacktickQuoteRange(Line, I)) {
Out.appendText(Line.substr(0, I));
Out.appendCode(Range->trim("`"));
return parseDocumentationLine(Line.substr(I+Range->size()), Out);
}
break;
}
}
Out.appendText(Line);
}
void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
std::vector<llvm::StringRef> ParagraphLines;
auto FlushParagraph = [&] {
@ -845,7 +892,7 @@ void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
return;
auto &P = Output.addParagraph();
for (llvm::StringRef Line : ParagraphLines)
P.appendText(Line.str());
parseDocumentationLine(Line, P);
ParagraphLines.clear();
};

View File

@ -1958,7 +1958,7 @@ def)",
}
}
TEST(Hover, DocCommentLineBreakConversion) {
TEST(Hover, ParseDocumentation) {
struct Case {
llvm::StringRef Documentation;
llvm::StringRef ExpectedRenderMarkdown;
@ -2017,6 +2017,22 @@ TEST(Hover, DocCommentLineBreakConversion) {
"foo\nbar",
"foo bar",
"foo bar",
},
{
// FIXME: we insert spaces between code and text chunk.
"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",
},
{
"`not\nparsed`",
"\\`not parsed\\`",
"`not parsed`",
}};
for (const auto &C : Cases) {