diff options
Diffstat (limited to 'clang-tools-extra/clangd/FormattedString.cpp')
| -rw-r--r-- | clang-tools-extra/clangd/FormattedString.cpp | 205 |
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 |

