summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd/FormattedString.cpp
diff options
context:
space:
mode:
authorKadir Cetinkaya <kadircet@google.com>2019-12-10 10:28:37 +0100
committerKadir Cetinkaya <kadircet@google.com>2019-12-13 09:58:55 +0100
commit597c6b65552a777a40f2afed07c543f6789318b1 (patch)
tree27cacf05fda0f5b7ec8a585b2c478a61cfe28436 /clang-tools-extra/clangd/FormattedString.cpp
parent86e652f8284d44c0e3fa9b27421a671e89bfe0a0 (diff)
downloadbcm5719-llvm-597c6b65552a777a40f2afed07c543f6789318b1.tar.gz
bcm5719-llvm-597c6b65552a777a40f2afed07c543f6789318b1.zip
[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
Diffstat (limited to 'clang-tools-extra/clangd/FormattedString.cpp')
-rw-r--r--clang-tools-extra/clangd/FormattedString.cpp205
1 files changed, 111 insertions, 94 deletions
diff --git a/clang-tools-extra/clangd/FormattedString.cpp b/clang-tools-extra/clangd/FormattedString.cpp
index 27fd37b9ae6..8bb17f38012 100644
--- a/clang-tools-extra/clangd/FormattedString.cpp
+++ b/clang-tools-extra/clangd/FormattedString.cpp
@@ -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;
}
-} // namespace
-
-void FormattedString::appendText(std::string Text) {
- Chunk C;
- C.Kind = ChunkKind::PlainText;
- C.Contents = Text;
- Chunks.push_back(C);
+// 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;
}
-void FormattedString::appendCodeBlock(std::string Code, std::string Language) {
- Chunk C;
- C.Kind = ChunkKind::CodeBlock;
- C.Contents = std::move(Code);
- C.Language = std::move(Language);
- Chunks.push_back(std::move(C));
+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();
}
-void FormattedString::appendInlineCode(std::string Code) {
- Chunk C;
- C.Kind = ChunkKind::InlineCodeBlock;
- C.Contents = std::move(Code);
- Chunks.push_back(std::move(C));
-}
+// 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
-std::string FormattedString::renderAsMarkdown() const {
+std::string Block::asMarkdown() 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;
+ llvm::raw_string_ostream OS(R);
+ renderMarkdown(OS);
+ return llvm::StringRef(OS.str()).trim().str();
}
-std::string FormattedString::renderAsPlainText() const {
+std::string Block::asPlainText() 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;
+ 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 ChunkKind::PlainText:
- EnsureWhitespace();
- R += C.Contents;
+ case Chunk::PlainText:
+ OS << renderText(C.Contents);
break;
- case ChunkKind::InlineCodeBlock:
- EnsureWhitespace();
- R += C.Contents;
- break;
- case ChunkKind::CodeBlock:
- R += C.Contents;
+ case Chunk::InlineCode:
+ OS << renderInlineBlock(C.Contents);
break;
}
- // Trim trailing whitespace in chunk.
- while (!R.empty() && isWhitespace(R.back()))
- R.pop_back();
+ Sep = " ";
}
- return R;
+ // Paragraphs are translated into markdown lines, not markdown paragraphs.
+ // Therefore it only has a single linebreak afterwards.
+ OS << '\n';
}
-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;
- }
+void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
+ llvm::StringRef Sep = "";
+ for (auto &C : Chunks) {
+ OS << Sep << C.Contents;
+ Sep = " ";
}
- while (!R.empty() && isWhitespace(R.back()))
- R.pop_back();
- return R;
+ 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.Kind = Chunk::InlineCode;
+ return *this;
+}
+
+Paragraph &Document::addParagraph() {
+ Children.push_back(std::make_unique<Paragraph>());
+ return *static_cast<Paragraph *>(Children.back().get());
+}
+
+void Document::addSpacer() { Children.push_back(std::make_unique<Spacer>()); }
+
+std::string Document::asMarkdown() const {
+ return renderBlocks(Children, &Block::renderMarkdown);
+}
+
+std::string Document::asPlainText() const {
+ return renderBlocks(Children, &Block::renderPlainText);
}
+} // namespace markup
} // namespace clangd
} // namespace clang
OpenPOWER on IntegriCloud