mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-02-18 16:58:23 +00:00
[clangd] Introduce paragraph, the first part of new rendering structs
Summary: Initial patch for new rendering structs in clangd. Splitting implementation into smaller chunks, for a full view of the API see D71063. Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D71248 Reviewers: sammccall
This commit is contained in:
parent
86e652f828
commit
597c6b6555
@ -1095,10 +1095,10 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
|
||||
R.range = (*H)->SymRange;
|
||||
switch (HoverContentFormat) {
|
||||
case MarkupKind::PlainText:
|
||||
R.contents.value = (*H)->present().renderAsPlainText();
|
||||
R.contents.value = (*H)->present().asPlainText();
|
||||
return Reply(std::move(R));
|
||||
case MarkupKind::Markdown:
|
||||
R.contents.value = (*H)->present().renderAsMarkdown();
|
||||
R.contents.value = (*H)->present().asMarkdown();
|
||||
return Reply(std::move(R));
|
||||
};
|
||||
llvm_unreachable("unhandled MarkupKind");
|
||||
|
@ -7,19 +7,27 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "FormattedString.h"
|
||||
#include "clang/Basic/CharInfo.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/ErrorHandling.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace markup {
|
||||
|
||||
namespace {
|
||||
/// Escape a markdown text block. Ensures the punctuation will not introduce
|
||||
/// any of the markdown constructs.
|
||||
static std::string renderText(llvm::StringRef Input) {
|
||||
std::string renderText(llvm::StringRef Input) {
|
||||
// Escaping ASCII punctiation ensures we can't start a markdown construct.
|
||||
constexpr llvm::StringLiteral Punctuation =
|
||||
R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
|
||||
@ -40,7 +48,7 @@ static std::string renderText(llvm::StringRef Input) {
|
||||
|
||||
/// Renders \p Input as an inline block of code in markdown. The returned value
|
||||
/// is surrounded by backticks and the inner contents are properly escaped.
|
||||
static std::string renderInlineBlock(llvm::StringRef Input) {
|
||||
std::string renderInlineBlock(llvm::StringRef Input) {
|
||||
std::string R;
|
||||
// Double all backticks to make sure we don't close the inline block early.
|
||||
for (size_t From = 0; From < Input.size();) {
|
||||
@ -63,11 +71,11 @@ static std::string renderInlineBlock(llvm::StringRef Input) {
|
||||
return "` " + std::move(R) + " `";
|
||||
return "`" + std::move(R) + "`";
|
||||
}
|
||||
|
||||
/// Render \p Input as markdown code block with a specified \p Language. The
|
||||
/// result is surrounded by >= 3 backticks. Although markdown also allows to use
|
||||
/// '~' for code blocks, they are never used.
|
||||
static std::string renderCodeBlock(llvm::StringRef Input,
|
||||
llvm::StringRef Language) {
|
||||
std::string renderCodeBlock(llvm::StringRef Input, llvm::StringRef Language) {
|
||||
// Count the maximum number of consecutive backticks in \p Input. We need to
|
||||
// start and end the code block with more.
|
||||
unsigned MaxBackticks = 0;
|
||||
@ -86,114 +94,123 @@ static std::string renderCodeBlock(llvm::StringRef Input,
|
||||
return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker;
|
||||
}
|
||||
|
||||
// Trims the input and concatanates 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();
|
||||
llvm::SmallVector<llvm::StringRef, 4> Words;
|
||||
llvm::SplitString(Input, Words);
|
||||
if (Words.empty())
|
||||
return "";
|
||||
// Go over each word and and add it to the string.
|
||||
for (llvm::StringRef Word : Words) {
|
||||
llvm::for_each(Word, [&WritePtr](const char C) { *WritePtr++ = C; });
|
||||
// Separate from next block.
|
||||
*WritePtr++ = ' ';
|
||||
}
|
||||
// Get rid of extra spaces, -1 is for the trailing space introduced with last
|
||||
// word.
|
||||
Input.resize(WritePtr - Input.begin() - 1);
|
||||
return Input;
|
||||
}
|
||||
|
||||
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);
|
||||
for (auto &C : Children)
|
||||
((*C).*RenderFunc)(OS);
|
||||
return llvm::StringRef(OS.str()).trim().str();
|
||||
}
|
||||
|
||||
// Puts a vertical space between blocks inside a document.
|
||||
class Spacer : public Block {
|
||||
public:
|
||||
void renderMarkdown(llvm::raw_ostream &OS) const override { OS << '\n'; }
|
||||
void renderPlainText(llvm::raw_ostream &OS) const override { OS << '\n'; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void FormattedString::appendText(std::string Text) {
|
||||
Chunk C;
|
||||
C.Kind = ChunkKind::PlainText;
|
||||
C.Contents = Text;
|
||||
Chunks.push_back(C);
|
||||
std::string Block::asMarkdown() const {
|
||||
std::string R;
|
||||
llvm::raw_string_ostream OS(R);
|
||||
renderMarkdown(OS);
|
||||
return llvm::StringRef(OS.str()).trim().str();
|
||||
}
|
||||
|
||||
void FormattedString::appendCodeBlock(std::string Code, std::string Language) {
|
||||
Chunk C;
|
||||
C.Kind = ChunkKind::CodeBlock;
|
||||
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 {
|
||||
llvm::StringRef Sep = "";
|
||||
for (auto &C : Chunks) {
|
||||
OS << Sep;
|
||||
switch (C.Kind) {
|
||||
case Chunk::PlainText:
|
||||
OS << renderText(C.Contents);
|
||||
break;
|
||||
case Chunk::InlineCode:
|
||||
OS << renderInlineBlock(C.Contents);
|
||||
break;
|
||||
}
|
||||
Sep = " ";
|
||||
}
|
||||
// Paragraphs are translated into markdown lines, not markdown paragraphs.
|
||||
// Therefore it only has a single linebreak afterwards.
|
||||
OS << '\n';
|
||||
}
|
||||
|
||||
void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
|
||||
llvm::StringRef Sep = "";
|
||||
for (auto &C : Chunks) {
|
||||
OS << Sep << C.Contents;
|
||||
Sep = " ";
|
||||
}
|
||||
OS << '\n';
|
||||
}
|
||||
|
||||
Paragraph &Paragraph::appendText(std::string Text) {
|
||||
Text = canonicalizeSpaces(std::move(Text));
|
||||
if (Text.empty())
|
||||
return *this;
|
||||
Chunks.emplace_back();
|
||||
Chunk &C = Chunks.back();
|
||||
C.Contents = std::move(Text);
|
||||
C.Kind = Chunk::PlainText;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Paragraph &Paragraph::appendCode(std::string Code) {
|
||||
Code = canonicalizeSpaces(std::move(Code));
|
||||
if (Code.empty())
|
||||
return *this;
|
||||
Chunks.emplace_back();
|
||||
Chunk &C = Chunks.back();
|
||||
C.Contents = std::move(Code);
|
||||
C.Language = std::move(Language);
|
||||
Chunks.push_back(std::move(C));
|
||||
C.Kind = Chunk::InlineCode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FormattedString::appendInlineCode(std::string Code) {
|
||||
Chunk C;
|
||||
C.Kind = ChunkKind::InlineCodeBlock;
|
||||
C.Contents = std::move(Code);
|
||||
Chunks.push_back(std::move(C));
|
||||
Paragraph &Document::addParagraph() {
|
||||
Children.push_back(std::make_unique<Paragraph>());
|
||||
return *static_cast<Paragraph *>(Children.back().get());
|
||||
}
|
||||
|
||||
std::string FormattedString::renderAsMarkdown() const {
|
||||
std::string R;
|
||||
auto EnsureWhitespace = [&R]() {
|
||||
// Adds a space for nicer rendering.
|
||||
if (!R.empty() && !isWhitespace(R.back()))
|
||||
R += " ";
|
||||
};
|
||||
for (const auto &C : Chunks) {
|
||||
switch (C.Kind) {
|
||||
case ChunkKind::PlainText:
|
||||
if (!C.Contents.empty() && !isWhitespace(C.Contents.front()))
|
||||
EnsureWhitespace();
|
||||
R += renderText(C.Contents);
|
||||
continue;
|
||||
case ChunkKind::InlineCodeBlock:
|
||||
EnsureWhitespace();
|
||||
R += renderInlineBlock(C.Contents);
|
||||
continue;
|
||||
case ChunkKind::CodeBlock:
|
||||
if (!R.empty() && !llvm::StringRef(R).endswith("\n"))
|
||||
R += "\n";
|
||||
R += renderCodeBlock(C.Contents, C.Language);
|
||||
R += "\n";
|
||||
continue;
|
||||
}
|
||||
llvm_unreachable("unhanlded ChunkKind");
|
||||
}
|
||||
return R;
|
||||
void Document::addSpacer() { Children.push_back(std::make_unique<Spacer>()); }
|
||||
|
||||
std::string Document::asMarkdown() const {
|
||||
return renderBlocks(Children, &Block::renderMarkdown);
|
||||
}
|
||||
|
||||
std::string FormattedString::renderAsPlainText() const {
|
||||
std::string R;
|
||||
auto EnsureWhitespace = [&]() {
|
||||
if (R.empty() || isWhitespace(R.back()))
|
||||
return;
|
||||
R += " ";
|
||||
};
|
||||
Optional<bool> LastWasBlock;
|
||||
for (const auto &C : Chunks) {
|
||||
bool IsBlock = C.Kind == ChunkKind::CodeBlock;
|
||||
if (LastWasBlock.hasValue() && (IsBlock || *LastWasBlock))
|
||||
R += "\n\n";
|
||||
LastWasBlock = IsBlock;
|
||||
|
||||
switch (C.Kind) {
|
||||
case ChunkKind::PlainText:
|
||||
EnsureWhitespace();
|
||||
R += C.Contents;
|
||||
break;
|
||||
case ChunkKind::InlineCodeBlock:
|
||||
EnsureWhitespace();
|
||||
R += C.Contents;
|
||||
break;
|
||||
case ChunkKind::CodeBlock:
|
||||
R += C.Contents;
|
||||
break;
|
||||
}
|
||||
// Trim trailing whitespace in chunk.
|
||||
while (!R.empty() && isWhitespace(R.back()))
|
||||
R.pop_back();
|
||||
}
|
||||
return R;
|
||||
}
|
||||
|
||||
std::string FormattedString::renderForTests() const {
|
||||
std::string R;
|
||||
for (const auto &C : Chunks) {
|
||||
switch (C.Kind) {
|
||||
case ChunkKind::PlainText:
|
||||
R += "text[" + C.Contents + "]";
|
||||
break;
|
||||
case ChunkKind::InlineCodeBlock:
|
||||
R += "code[" + C.Contents + "]";
|
||||
break;
|
||||
case ChunkKind::CodeBlock:
|
||||
if (!R.empty())
|
||||
R += "\n";
|
||||
R += llvm::formatv("codeblock({0}) [\n{1}\n]\n", C.Language, C.Contents);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!R.empty() && isWhitespace(R.back()))
|
||||
R.pop_back();
|
||||
return R;
|
||||
std::string Document::asPlainText() const {
|
||||
return renderBlocks(Children, &Block::renderPlainText);
|
||||
}
|
||||
} // namespace markup
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
@ -13,38 +13,48 @@
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H
|
||||
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace markup {
|
||||
|
||||
/// A structured string representation that could be converted to markdown or
|
||||
/// plaintext upon requrest.
|
||||
class FormattedString {
|
||||
/// Holds text and knows how to lay it out. Multiple blocks can be grouped to
|
||||
/// form a document. Blocks include their own trailing newlines, container
|
||||
/// should trim them if need be.
|
||||
class Block {
|
||||
public:
|
||||
/// Append plain text to the end of the string.
|
||||
void appendText(std::string Text);
|
||||
/// Append a block of C++ code. This translates to a ``` block in markdown.
|
||||
/// In a plain text representation, the code block will be surrounded by
|
||||
/// newlines.
|
||||
void appendCodeBlock(std::string Code, std::string Language = "cpp");
|
||||
/// Append an inline block of C++ code. This translates to the ` block in
|
||||
/// markdown.
|
||||
void appendInlineCode(std::string Code);
|
||||
virtual void renderMarkdown(llvm::raw_ostream &OS) const = 0;
|
||||
virtual void renderPlainText(llvm::raw_ostream &OS) const = 0;
|
||||
std::string asMarkdown() const;
|
||||
std::string asPlainText() const;
|
||||
|
||||
std::string renderAsMarkdown() const;
|
||||
std::string renderAsPlainText() const;
|
||||
std::string renderForTests() const;
|
||||
virtual ~Block() = default;
|
||||
};
|
||||
|
||||
/// Represents parts of the markup that can contain strings, like inline code,
|
||||
/// code block or plain text.
|
||||
/// One must introduce different paragraphs to create separate blocks.
|
||||
class Paragraph : public Block {
|
||||
public:
|
||||
void renderMarkdown(llvm::raw_ostream &OS) const override;
|
||||
void renderPlainText(llvm::raw_ostream &OS) const override;
|
||||
|
||||
/// Append plain text to the end of the string.
|
||||
Paragraph &appendText(std::string Text);
|
||||
|
||||
/// Append inline code, this translates to the ` block in markdown.
|
||||
Paragraph &appendCode(std::string Code);
|
||||
|
||||
private:
|
||||
enum class ChunkKind {
|
||||
PlainText, /// A plain text paragraph.
|
||||
CodeBlock, /// A block of code.
|
||||
InlineCodeBlock, /// An inline block of code.
|
||||
};
|
||||
struct Chunk {
|
||||
ChunkKind Kind = ChunkKind::PlainText;
|
||||
enum {
|
||||
PlainText,
|
||||
InlineCode,
|
||||
} Kind = PlainText;
|
||||
std::string Contents;
|
||||
/// Language for code block chunks. Ignored for other chunks.
|
||||
std::string Language;
|
||||
@ -52,6 +62,23 @@ private:
|
||||
std::vector<Chunk> Chunks;
|
||||
};
|
||||
|
||||
/// A format-agnostic representation for structured text. Allows rendering into
|
||||
/// markdown and plaintext.
|
||||
class Document {
|
||||
public:
|
||||
/// Adds a semantical block that will be separate from others.
|
||||
Paragraph &addParagraph();
|
||||
/// Inserts a vertical space into the document.
|
||||
void addSpacer();
|
||||
|
||||
std::string asMarkdown() const;
|
||||
std::string asPlainText() const;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Block>> Children;
|
||||
};
|
||||
|
||||
} // namespace markup
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "AST.h"
|
||||
#include "CodeCompletionStrings.h"
|
||||
#include "FindTarget.h"
|
||||
#include "FormattedString.h"
|
||||
#include "Logger.h"
|
||||
#include "Selection.h"
|
||||
#include "SourceCode.h"
|
||||
@ -441,28 +442,30 @@ llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
|
||||
return HI;
|
||||
}
|
||||
|
||||
FormattedString HoverInfo::present() const {
|
||||
FormattedString Output;
|
||||
markup::Document HoverInfo::present() const {
|
||||
markup::Document Output;
|
||||
if (NamespaceScope) {
|
||||
Output.appendText("Declared in");
|
||||
auto &P = Output.addParagraph();
|
||||
P.appendText("Declared in");
|
||||
// Drop trailing "::".
|
||||
if (!LocalScope.empty())
|
||||
Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2));
|
||||
P.appendCode(llvm::StringRef(LocalScope).drop_back(2));
|
||||
else if (NamespaceScope->empty())
|
||||
Output.appendInlineCode("global namespace");
|
||||
P.appendCode("global namespace");
|
||||
else
|
||||
Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
|
||||
P.appendCode(llvm::StringRef(*NamespaceScope).drop_back(2));
|
||||
}
|
||||
|
||||
Output.addSpacer();
|
||||
if (!Definition.empty()) {
|
||||
Output.appendCodeBlock(Definition);
|
||||
Output.addParagraph().appendCode(Definition);
|
||||
} else {
|
||||
// Builtin types
|
||||
Output.appendCodeBlock(Name);
|
||||
Output.addParagraph().appendCode(Name);
|
||||
}
|
||||
|
||||
if (!Documentation.empty())
|
||||
Output.appendText(Documentation);
|
||||
Output.addParagraph().appendText(Documentation);
|
||||
return Output;
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ struct HoverInfo {
|
||||
llvm::Optional<std::string> Value;
|
||||
|
||||
/// Produce a user-readable information.
|
||||
FormattedString present() const;
|
||||
markup::Document present() const;
|
||||
};
|
||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
|
||||
inline bool operator==(const HoverInfo::Param &LHS,
|
||||
|
@ -8,192 +8,109 @@
|
||||
#include "FormattedString.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace markup {
|
||||
namespace {
|
||||
|
||||
TEST(FormattedString, Basic) {
|
||||
FormattedString S;
|
||||
EXPECT_EQ(S.renderAsPlainText(), "");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "");
|
||||
|
||||
S.appendText("foobar ");
|
||||
S.appendText("baz");
|
||||
EXPECT_EQ(S.renderAsPlainText(), "foobar baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foobar baz");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foobar");
|
||||
EXPECT_EQ(S.renderAsPlainText(), "foobar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foobar`");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foobar");
|
||||
EXPECT_EQ(S.renderAsPlainText(), "foobar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
|
||||
"foobar\n"
|
||||
"```\n");
|
||||
}
|
||||
|
||||
TEST(FormattedString, CodeBlocks) {
|
||||
FormattedString S;
|
||||
S.appendCodeBlock("foobar");
|
||||
S.appendCodeBlock("bazqux", "javascript");
|
||||
S.appendText("after");
|
||||
|
||||
std::string ExpectedText = R"(foobar
|
||||
|
||||
bazqux
|
||||
|
||||
after)";
|
||||
EXPECT_EQ(S.renderAsPlainText(), ExpectedText);
|
||||
std::string ExpectedMarkdown = R"md(```cpp
|
||||
foobar
|
||||
```
|
||||
```javascript
|
||||
bazqux
|
||||
```
|
||||
after)md";
|
||||
EXPECT_EQ(S.renderAsMarkdown(), ExpectedMarkdown);
|
||||
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foobar");
|
||||
S.appendInlineCode("bazqux");
|
||||
EXPECT_EQ(S.renderAsPlainText(), "foobar bazqux");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foobar` `bazqux`");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendText("foo");
|
||||
S.appendInlineCode("bar");
|
||||
S.appendText("baz");
|
||||
|
||||
EXPECT_EQ(S.renderAsPlainText(), "foo bar baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foo `bar` baz");
|
||||
}
|
||||
|
||||
TEST(FormattedString, Escaping) {
|
||||
TEST(Render, Escaping) {
|
||||
// Check some ASCII punctuation
|
||||
FormattedString S;
|
||||
S.appendText("*!`");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "\\*\\!\\`");
|
||||
Paragraph P;
|
||||
P.appendText("*!`");
|
||||
EXPECT_EQ(P.asMarkdown(), "\\*\\!\\`");
|
||||
|
||||
// Check all ASCII punctuation.
|
||||
S = FormattedString();
|
||||
P = Paragraph();
|
||||
std::string Punctuation = R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
|
||||
// Same text, with each character escaped.
|
||||
std::string EscapedPunctuation;
|
||||
EscapedPunctuation.reserve(2 * Punctuation.size());
|
||||
for (char C : Punctuation)
|
||||
EscapedPunctuation += std::string("\\") + C;
|
||||
S.appendText(Punctuation);
|
||||
EXPECT_EQ(S.renderAsMarkdown(), EscapedPunctuation);
|
||||
P.appendText(Punctuation);
|
||||
EXPECT_EQ(P.asMarkdown(), EscapedPunctuation);
|
||||
|
||||
// In code blocks we don't need to escape ASCII punctuation.
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("* foo !+ bar * baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`* foo !+ bar * baz`");
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("#define FOO\n* foo !+ bar * baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
|
||||
"#define FOO\n* foo !+ bar * baz\n"
|
||||
"```\n");
|
||||
P = Paragraph();
|
||||
P.appendCode("* foo !+ bar * baz");
|
||||
EXPECT_EQ(P.asMarkdown(), "`* foo !+ bar * baz`");
|
||||
|
||||
// But we have to escape the backticks.
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foo`bar`baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foo``bar``baz`");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foo`bar`baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
|
||||
"foo`bar`baz\n"
|
||||
"```\n");
|
||||
P = Paragraph();
|
||||
P.appendCode("foo`bar`baz");
|
||||
EXPECT_EQ(P.asMarkdown(), "`foo``bar``baz`");
|
||||
|
||||
// Inline code blocks starting or ending with backticks should add spaces.
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("`foo");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "` ``foo `");
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foo`");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "` foo`` `");
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("`foo`");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "` ``foo`` `");
|
||||
|
||||
// Should also add extra spaces if the block stars and ends with spaces.
|
||||
S = FormattedString();
|
||||
S.appendInlineCode(" foo ");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "` foo `");
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foo ");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foo `");
|
||||
S = FormattedString();
|
||||
S.appendInlineCode(" foo");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "` foo`");
|
||||
|
||||
// Code blocks might need more than 3 backticks.
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foobarbaz `\nqux");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
|
||||
"foobarbaz `\nqux\n"
|
||||
"```\n");
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foobarbaz ``\nqux");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "```cpp\n"
|
||||
"foobarbaz ``\nqux\n"
|
||||
"```\n");
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foobarbaz ```\nqux");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "````cpp\n"
|
||||
"foobarbaz ```\nqux\n"
|
||||
"````\n");
|
||||
S = FormattedString();
|
||||
S.appendCodeBlock("foobarbaz ` `` ``` ```` `\nqux");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`````cpp\n"
|
||||
"foobarbaz ` `` ``` ```` `\nqux\n"
|
||||
"`````\n");
|
||||
P = Paragraph();
|
||||
P.appendCode("`foo");
|
||||
EXPECT_EQ(P.asMarkdown(), "` ``foo `");
|
||||
P = Paragraph();
|
||||
P.appendCode("foo`");
|
||||
EXPECT_EQ(P.asMarkdown(), "` foo`` `");
|
||||
P = Paragraph();
|
||||
P.appendCode("`foo`");
|
||||
EXPECT_EQ(P.asMarkdown(), "` ``foo`` `");
|
||||
}
|
||||
|
||||
TEST(FormattedString, MarkdownWhitespace) {
|
||||
// Whitespace should be added as separators between blocks.
|
||||
FormattedString S;
|
||||
S.appendText("foo");
|
||||
S.appendText("bar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foo bar");
|
||||
TEST(Paragraph, SeparationOfChunks) {
|
||||
// This test keeps appending contents to a single Paragraph and checks
|
||||
// expected accumulated contents after each one.
|
||||
// Purpose is to check for separation between different chunks.
|
||||
Paragraph P;
|
||||
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foo");
|
||||
S.appendInlineCode("bar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foo` `bar`");
|
||||
P.appendText("after");
|
||||
EXPECT_EQ(P.asMarkdown(), "after");
|
||||
EXPECT_EQ(P.asPlainText(), "after");
|
||||
|
||||
// However, we don't want to add any extra whitespace.
|
||||
S = FormattedString();
|
||||
S.appendText("foo ");
|
||||
S.appendInlineCode("bar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foo `bar`");
|
||||
P.appendCode("foobar");
|
||||
EXPECT_EQ(P.asMarkdown(), "after `foobar`");
|
||||
EXPECT_EQ(P.asPlainText(), "after foobar");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendText("foo\n");
|
||||
S.appendInlineCode("bar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foo\n`bar`");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendInlineCode("foo");
|
||||
S.appendText(" bar");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "`foo` bar");
|
||||
|
||||
S = FormattedString();
|
||||
S.appendText("foo");
|
||||
S.appendCodeBlock("bar");
|
||||
S.appendText("baz");
|
||||
EXPECT_EQ(S.renderAsMarkdown(), "foo\n```cpp\nbar\n```\nbaz");
|
||||
P.appendText("bat");
|
||||
EXPECT_EQ(P.asMarkdown(), "after `foobar` bat");
|
||||
EXPECT_EQ(P.asPlainText(), "after foobar bat");
|
||||
}
|
||||
|
||||
TEST(Paragraph, ExtraSpaces) {
|
||||
// Make sure spaces inside chunks are dropped.
|
||||
Paragraph P;
|
||||
P.appendText("foo\n \t baz");
|
||||
P.appendCode(" bar\n");
|
||||
EXPECT_EQ(P.asMarkdown(), R"md(foo baz `bar`)md");
|
||||
EXPECT_EQ(P.asPlainText(), R"pt(foo baz bar)pt");
|
||||
}
|
||||
|
||||
TEST(Paragraph, NewLines) {
|
||||
// New lines before and after chunks are dropped.
|
||||
Paragraph P;
|
||||
P.appendText(" \n foo\nbar\n ");
|
||||
P.appendCode(" \n foo\nbar \n ");
|
||||
EXPECT_EQ(P.asMarkdown(), R"md(foo bar `foo bar`)md");
|
||||
EXPECT_EQ(P.asPlainText(), R"pt(foo bar foo bar)pt");
|
||||
}
|
||||
|
||||
TEST(Document, Separators) {
|
||||
Document D;
|
||||
D.addParagraph().appendText("foo");
|
||||
D.addParagraph().appendText("bar");
|
||||
EXPECT_EQ(D.asMarkdown(), "foo\nbar");
|
||||
EXPECT_EQ(D.asPlainText(), "foo\nbar");
|
||||
}
|
||||
|
||||
TEST(Document, Spacer) {
|
||||
Document D;
|
||||
D.addParagraph().appendText("foo");
|
||||
D.addSpacer();
|
||||
D.addParagraph().appendText("bar");
|
||||
EXPECT_EQ(D.asMarkdown(), "foo\n\nbar");
|
||||
EXPECT_EQ(D.asPlainText(), "foo\n\nbar");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace markup
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
Loading…
x
Reference in New Issue
Block a user