[clangd] Introduce bulletlists

Reviewers: sammccall

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

Tags: #clang

Differential Revision: https://reviews.llvm.org/D71422
This commit is contained in:
Kadir Cetinkaya 2019-12-12 15:25:45 +01:00
parent e34801c8e6
commit a000f2e53f
No known key found for this signature in database
GPG Key ID: E39E36B8D2057ED6
3 changed files with 125 additions and 1 deletions

View File

@ -149,6 +149,21 @@ private:
std::string Contents;
std::string Language;
};
// Inserts two spaces after each `\n` to indent each line. First line is not
// indented.
std::string indentLines(llvm::StringRef Input) {
assert(!Input.endswith("\n") && "Input should've been trimmed.");
std::string IndentedR;
// We'll add 2 spaces after each new line.
IndentedR.reserve(Input.size() + Input.count('\n') * 2);
for (char C : Input) {
IndentedR += C;
if (C == '\n')
IndentedR.append(" ");
}
return IndentedR;
}
} // namespace
std::string Block::asMarkdown() const {
@ -193,6 +208,24 @@ void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
OS << '\n';
}
void BulletList::renderMarkdown(llvm::raw_ostream &OS) const {
for (auto &D : Items) {
// Instead of doing this we might prefer passing Indent to children to get
// rid of the copies, if it turns out to be a bottleneck.
OS << "- " << indentLines(D.asMarkdown()) << '\n';
}
// We need a new line after list to terminate it in markdown.
OS << '\n';
}
void BulletList::renderPlainText(llvm::raw_ostream &OS) const {
for (auto &D : Items) {
// Instead of doing this we might prefer passing Indent to children to get
// rid of the copies, if it turns out to be a bottleneck.
OS << "- " << indentLines(D.asPlainText()) << '\n';
}
}
Paragraph &Paragraph::appendText(std::string Text) {
Text = canonicalizeSpaces(std::move(Text));
if (Text.empty())
@ -215,6 +248,11 @@ Paragraph &Paragraph::appendCode(std::string Code) {
return *this;
}
class Document &BulletList::addItem() {
Items.emplace_back();
return Items.back();
}
Paragraph &Document::addParagraph() {
Children.push_back(std::make_unique<Paragraph>());
return *static_cast<Paragraph *>(Children.back().get());
@ -234,6 +272,11 @@ std::string Document::asMarkdown() const {
std::string Document::asPlainText() const {
return renderBlocks(Children, &Block::renderPlainText);
}
BulletList &Document::addBulletList() {
Children.emplace_back(std::make_unique<BulletList>());
return *static_cast<BulletList *>(Children.back().get());
}
} // namespace markup
} // namespace clangd
} // namespace clang

View File

@ -62,6 +62,19 @@ private:
std::vector<Chunk> Chunks;
};
/// Represents a sequence of one or more documents. Knows how to print them in a
/// list like format, e.g. by prepending with "- " and indentation.
class BulletList : public Block {
public:
void renderMarkdown(llvm::raw_ostream &OS) const override;
void renderPlainText(llvm::raw_ostream &OS) const override;
class Document &addItem();
private:
std::vector<class Document> Items;
};
/// A format-agnostic representation for structured text. Allows rendering into
/// markdown and plaintext.
class Document {
@ -74,13 +87,16 @@ public:
/// text representation, the code block will be surrounded by newlines.
void addCodeBlock(std::string Code, std::string Language = "cpp");
BulletList &addBulletList();
/// Doesn't contain any trailing newlines.
std::string asMarkdown() const;
/// Doesn't contain any trailing newlines.
std::string asPlainText() const;
private:
std::vector<std::unique_ptr<Block>> Children;
};
} // namespace markup
} // namespace clangd
} // namespace clang

View File

@ -186,6 +186,71 @@ foo)pt";
EXPECT_EQ(D.asPlainText(), ExpectedPlainText);
}
TEST(BulletList, Render) {
BulletList L;
// Flat list
L.addItem().addParagraph().appendText("foo");
EXPECT_EQ(L.asMarkdown(), "- foo");
EXPECT_EQ(L.asPlainText(), "- foo");
L.addItem().addParagraph().appendText("bar");
EXPECT_EQ(L.asMarkdown(), R"md(- foo
- bar)md");
EXPECT_EQ(L.asPlainText(), R"pt(- foo
- bar)pt");
// Nested list, with a single item.
Document &D = L.addItem();
// First item with foo\nbaz
D.addParagraph().appendText("foo");
D.addParagraph().appendText("baz");
// Nest one level.
Document &Inner = D.addBulletList().addItem();
Inner.addParagraph().appendText("foo");
// Nest one more level.
BulletList &InnerList = Inner.addBulletList();
// Single item, baz\nbaz
Document &DeepDoc = InnerList.addItem();
DeepDoc.addParagraph().appendText("baz");
DeepDoc.addParagraph().appendText("baz");
EXPECT_EQ(L.asMarkdown(), R"md(- foo
- bar
- foo
baz
- foo
- baz
baz)md");
EXPECT_EQ(L.asPlainText(), R"pt(- foo
- bar
- foo
baz
- foo
- baz
baz)pt");
// Termination
Inner.addParagraph().appendText("after");
EXPECT_EQ(L.asMarkdown(), R"md(- foo
- bar
- foo
baz
- foo
- baz
baz
after)md");
EXPECT_EQ(L.asPlainText(), R"pt(- foo
- bar
- foo
baz
- foo
- baz
baz
after)pt");
}
} // namespace
} // namespace markup
} // namespace clangd