#include "support/Markup.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/raw_ostream.h"
#include <cstddef>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
namespace markup {
namespace {
bool looksLikeTag(llvm::StringRef Contents) {
if (Contents.empty())
return false;
if (Contents.front() == '!' || Contents.front() == '?' ||
Contents.front() == '/')
return true;
if (!llvm::isAlpha(Contents.front()))
return false;
Contents = Contents
.drop_while([](char C) {
return llvm::isAlnum(C) || C == '-' || C == '_' || C == ':';
})
.drop_while(llvm::isSpace);
for (; !Contents.empty(); Contents = Contents.drop_front()) {
if (llvm::isAlnum(Contents.front()) || llvm::isSpace(Contents.front()))
continue;
if (Contents.front() == '>' || Contents.starts_with("/>"))
return true;
if (Contents.front() == '=')
return true;
return false;
}
return true;
}
bool needsLeadingEscape(char C, llvm::StringRef Before, llvm::StringRef After,
bool StartsLine) {
assert(Before.take_while(llvm::isSpace).empty());
auto RulerLength = [&]() -> unsigned {
if (!StartsLine || !Before.empty())
return false;
llvm::StringRef A = After.rtrim();
return llvm::all_of(A, [C](char D) { return C == D; }) ? 1 + A.size() : 0;
};
auto IsBullet = [&]() {
return StartsLine && Before.empty() &&
(After.empty() || After.starts_with(" "));
};
auto SpaceSurrounds = [&]() {
return (After.empty() || llvm::isSpace(After.front())) &&
(Before.empty() || llvm::isSpace(Before.back()));
};
auto WordSurrounds = [&]() {
return (!After.empty() && llvm::isAlnum(After.front())) &&
(!Before.empty() && llvm::isAlnum(Before.back()));
};
switch (C) {
case '\\':
return true;
case '`':
return true;
case '~':
return StartsLine && Before.empty() && After.starts_with("~~");
case '#': {
if (!StartsLine || !Before.empty())
return false;
llvm::StringRef Rest = After.ltrim(C);
return Rest.empty() || Rest.starts_with(" ");
}
case ']':
return After.starts_with(":") || After.starts_with("(");
case '=':
return RulerLength() > 0;
case '_':
if (RulerLength() >= 3)
return true;
return !(SpaceSurrounds() || WordSurrounds());
case '-':
if (RulerLength() > 0)
return true;
return IsBullet();
case '+':
return IsBullet();
case '*':
return IsBullet() || RulerLength() >= 3 || !SpaceSurrounds();
case '<':
return looksLikeTag(After);
case '>':
return StartsLine && Before.empty();
case '&': {
auto End = After.find(';');
if (End == llvm::StringRef::npos)
return false;
llvm::StringRef Content = After.substr(0, End);
if (Content.consume_front("#")) {
if (Content.consume_front("x") || Content.consume_front("X"))
return llvm::all_of(Content, llvm::isHexDigit);
return llvm::all_of(Content, llvm::isDigit);
}
return llvm::all_of(Content, llvm::isAlpha);
}
case '.':
case ')':
return StartsLine && !Before.empty() &&
llvm::all_of(Before, llvm::isDigit) && After.starts_with(" ");
default:
return false;
}
}
std::string renderText(llvm::StringRef Input, bool StartsLine) {
std::string R;
for (unsigned I = 0; I < Input.size(); ++I) {
if (needsLeadingEscape(Input[I], Input.substr(0, I), Input.substr(I + 1),
StartsLine))
R.push_back('\\');
R.push_back(Input[I]);
}
return R;
}
std::string renderInlineBlock(llvm::StringRef Input) {
std::string R;
for (size_t From = 0; From < Input.size();) {
size_t Next = Input.find("`", From);
R += Input.substr(From, Next - From);
if (Next == llvm::StringRef::npos)
break;
R += "``";
From = Next + 1;
}
if (llvm::StringRef(R).starts_with("`") || llvm::StringRef(R).ends_with("`"))
return "` " + std::move(R) + " `";
if (llvm::StringRef(R).starts_with(" ") && llvm::StringRef(R).ends_with(" "))
return "` " + std::move(R) + " `";
return "`" + std::move(R) + "`";
}
std::string getMarkerForCodeBlock(llvm::StringRef Input) {
unsigned MaxBackticks = 0;
unsigned Backticks = 0;
for (char C : Input) {
if (C == '`') {
++Backticks;
continue;
}
MaxBackticks = std::max(MaxBackticks, Backticks);
Backticks = 0;
}
MaxBackticks = std::max(Backticks, MaxBackticks);
return std::string(std::max(3u, MaxBackticks + 1), '`');
}
std::string canonicalizeSpaces(llvm::StringRef Input) {
llvm::SmallVector<llvm::StringRef> Words;
llvm::SplitString(Input, Words);
return llvm::join(Words, " ");
}
std::string renderBlocks(llvm::ArrayRef<std::unique_ptr<Block>> Children,
void (Block::*RenderFunc)(llvm::raw_ostream &) const) {
std::string R;
llvm::raw_string_ostream OS(R);
Children = Children.drop_while(
[](const std::unique_ptr<Block> &C) { return C->isRuler(); });
auto Last = llvm::find_if(
llvm::reverse(Children),
[](const std::unique_ptr<Block> &C) { return !C->isRuler(); });
Children = Children.drop_back(Children.end() - Last.base());
bool LastBlockWasRuler = true;
for (const auto &C : Children) {
if (C->isRuler() && LastBlockWasRuler)
continue;
LastBlockWasRuler = C->isRuler();
((*C).*RenderFunc)(OS);
}
std::string AdjustedResult;
llvm::StringRef TrimmedText(OS.str());
TrimmedText = TrimmedText.trim();
llvm::copy_if(TrimmedText, std::back_inserter(AdjustedResult),
[&TrimmedText](const char &C) {
return !llvm::StringRef(TrimmedText.data(),
&C - TrimmedText.data() + 1)
.ends_with("\n\n\n");
});
return AdjustedResult;
}
class Ruler : public Block {
public:
void renderMarkdown(llvm::raw_ostream &OS) const override {
OS << "\n---\n";
}
void renderPlainText(llvm::raw_ostream &OS) const override { OS << '\n'; }
std::unique_ptr<Block> clone() const override {
return std::make_unique<Ruler>(*this);
}
bool isRuler() const override { return true; }
};
class CodeBlock : public Block {
public:
void renderMarkdown(llvm::raw_ostream &OS) const override {
std::string Marker = getMarkerForCodeBlock(Contents);
OS << Marker << Language << '\n' << Contents << '\n' << Marker << '\n';
}
void renderPlainText(llvm::raw_ostream &OS) const override {
OS << '\n' << Contents << "\n\n";
}
std::unique_ptr<Block> clone() const override {
return std::make_unique<CodeBlock>(*this);
}
CodeBlock(std::string Contents, std::string Language)
: Contents(std::move(Contents)), Language(std::move(Language)) {}
private:
std::string Contents;
std::string Language;
};
std::string indentLines(llvm::StringRef Input) {
assert(!Input.ends_with("\n") && "Input should've been trimmed.");
std::string IndentedR;
IndentedR.reserve(Input.size() + Input.count('\n') * 2);
for (char C : Input) {
IndentedR += C;
if (C == '\n')
IndentedR.append(" ");
}
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;
};
}
std::string Block::asMarkdown() const {
std::string R;
llvm::raw_string_ostream OS(R);
renderMarkdown(OS);
return llvm::StringRef(OS.str()).trim().str();
}
std::string Block::asPlainText() const {
std::string R;
llvm::raw_string_ostream OS(R);
renderPlainText(OS);
return llvm::StringRef(OS.str()).trim().str();
}
void Paragraph::renderMarkdown(llvm::raw_ostream &OS) const {
bool NeedsSpace = false;
bool HasChunks = false;
for (auto &C : Chunks) {
if (C.SpaceBefore || NeedsSpace)
OS << " ";
switch (C.Kind) {
case Chunk::PlainText:
OS << renderText(C.Contents, !HasChunks);
break;
case Chunk::InlineCode:
OS << renderInlineBlock(C.Contents);
break;
}
HasChunks = true;
NeedsSpace = C.SpaceAfter;
}
OS << " \n";
}
std::unique_ptr<Block> Paragraph::clone() const {
return std::make_unique<Paragraph>(*this);
}
llvm::StringRef chooseMarker(llvm::ArrayRef<llvm::StringRef> Options,
llvm::StringRef 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 {
bool NeedsSpace = false;
for (auto &C : Chunks) {
if (C.SpaceBefore || NeedsSpace)
OS << " ";
llvm::StringRef Marker = "";
if (C.Preserve && C.Kind == Chunk::InlineCode)
Marker = chooseMarker({"`", "'", "\""}, C.Contents);
OS << Marker << C.Contents << Marker;
NeedsSpace = C.SpaceAfter;
}
OS << '\n';
}
BulletList::BulletList() = default;
BulletList::~BulletList() = default;
void BulletList::renderMarkdown(llvm::raw_ostream &OS) const {
for (auto &D : Items) {
OS << "- " << indentLines(D.asMarkdown()) << '\n';
}
OS << '\n';
}
void BulletList::renderPlainText(llvm::raw_ostream &OS) const {
for (auto &D : Items) {
OS << "- " << indentLines(D.asPlainText()) << '\n';
}
}
Paragraph &Paragraph::appendSpace() {
if (!Chunks.empty())
Chunks.back().SpaceAfter = true;
return *this;
}
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(Norm);
C.Kind = Chunk::PlainText;
C.SpaceBefore = llvm::isSpace(Text.front());
C.SpaceAfter = llvm::isSpace(Text.back());
return *this;
}
Paragraph &Paragraph::appendCode(llvm::StringRef Code, bool Preserve) {
bool AdjacentCode =
!Chunks.empty() && Chunks.back().Kind == Chunk::InlineCode;
std::string Norm = canonicalizeSpaces(std::move(Code));
if (Norm.empty())
return *this;
Chunks.emplace_back();
Chunk &C = Chunks.back();
C.Contents = std::move(Norm);
C.Kind = Chunk::InlineCode;
C.Preserve = Preserve;
C.SpaceBefore = AdjacentCode;
return *this;
}
std::unique_ptr<Block> BulletList::clone() const {
return std::make_unique<BulletList>(*this);
}
class Document &BulletList::addItem() {
Items.emplace_back();
return Items.back();
}
Document &Document::operator=(const Document &Other) {
Children.clear();
for (const auto &C : Other.Children)
Children.push_back(C->clone());
return *this;
}
void Document::append(Document Other) {
std::move(Other.Children.begin(), Other.Children.end(),
std::back_inserter(Children));
}
Paragraph &Document::addParagraph() {
Children.push_back(std::make_unique<Paragraph>());
return *static_cast<Paragraph *>(Children.back().get());
}
void Document::addRuler() { Children.push_back(std::make_unique<Ruler>()); }
void Document::addCodeBlock(std::string Code, std::string Language) {
Children.emplace_back(
std::make_unique<CodeBlock>(std::move(Code), std::move(Language)));
}
std::string Document::asMarkdown() const {
return renderBlocks(Children, &Block::renderMarkdown);
}
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());
}
Paragraph &Document::addHeading(size_t Level) {
assert(Level > 0);
Children.emplace_back(std::make_unique<Heading>(Level));
return *static_cast<Paragraph *>(Children.back().get());
}
}
}
}