240 lines · c
1//===--- Markup.h -------------------------------------------*- C++-*------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// A model of formatted text that can be rendered to plaintext or markdown.10//11//===----------------------------------------------------------------------===//12#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_MARKUP_H13#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_MARKUP_H14 15#include "llvm/Support/raw_ostream.h"16#include <cstddef>17#include <memory>18#include <string>19#include <vector>20 21namespace clang {22namespace clangd {23namespace markup {24 25/// Holds text and knows how to lay it out. Multiple blocks can be grouped to26/// form a document. Blocks include their own trailing newlines, container27/// should trim them if need be.28class Block {29public:30 virtual void renderEscapedMarkdown(llvm::raw_ostream &OS) const = 0;31 virtual void renderMarkdown(llvm::raw_ostream &OS) const = 0;32 virtual void renderPlainText(llvm::raw_ostream &OS) const = 0;33 virtual std::unique_ptr<Block> clone() const = 0;34 std::string asEscapedMarkdown() const;35 std::string asMarkdown() const;36 std::string asPlainText() const;37 38 virtual bool isRuler() const { return false; }39 virtual ~Block() = default;40};41 42/// Represents parts of the markup that can contain strings, like inline code,43/// code block or plain text.44/// One must introduce different paragraphs to create separate blocks.45class Paragraph : public Block {46public:47 void renderEscapedMarkdown(llvm::raw_ostream &OS) const override;48 void renderMarkdown(llvm::raw_ostream &OS) const override;49 void renderPlainText(llvm::raw_ostream &OS) const override;50 std::unique_ptr<Block> clone() const override;51 52 /// Append plain text to the end of the string.53 Paragraph &appendText(llvm::StringRef Text);54 55 /// Append emphasized text, this translates to the * block in markdown.56 Paragraph &appendEmphasizedText(llvm::StringRef Text);57 58 /// Append bold text, this translates to the ** block in markdown.59 Paragraph &appendBoldText(llvm::StringRef Text);60 61 /// Append inline code, this translates to the ` block in markdown.62 /// \p Preserve indicates the code span must be apparent even in plaintext.63 Paragraph &appendCode(llvm::StringRef Code, bool Preserve = false);64 65 /// Ensure there is space between the surrounding chunks.66 /// Has no effect at the beginning or end of a paragraph.67 Paragraph &appendSpace();68 69private:70 enum ChunkKind { PlainText, InlineCode, Bold, Emphasized };71 struct Chunk {72 ChunkKind Kind = PlainText;73 // Preserve chunk markers in plaintext.74 bool Preserve = false;75 std::string Contents;76 // Whether this chunk should be surrounded by whitespace.77 // Consecutive SpaceAfter and SpaceBefore will be collapsed into one space.78 // Code spans don't usually set this: their spaces belong "inside" the span.79 bool SpaceBefore = false;80 bool SpaceAfter = false;81 };82 std::vector<Chunk> Chunks;83 84 /// Estimated size of the string representation of this paragraph.85 /// Used to reserve space in the output string.86 /// Each time paragraph content is added, this value is updated.87 /// This is an estimate, so it may not be accurate but can help88 /// reducing dynamically reallocating string memory.89 unsigned EstimatedStringSize = 0;90 91 Paragraph &appendChunk(llvm::StringRef Contents, ChunkKind K);92 93 llvm::StringRef chooseMarker(llvm::ArrayRef<llvm::StringRef> Options,94 llvm::StringRef Text) const;95 96 /// Checks if the given line ends with punctuation that indicates a line break97 /// (.:,;!?).98 ///99 /// If \p IsMarkdown is false, lines ending with 2 spaces are also considered100 /// as indicating a line break. This is not needed for markdown because the101 /// client renderer will handle this case.102 bool punctuationIndicatesLineBreak(llvm::StringRef Line,103 bool IsMarkdown) const;104 105 /// Checks if the given line starts with a hard line break indicator.106 ///107 /// If \p IsMarkdown is true, only '@' and '\' are considered as indicators.108 /// Otherwise, '-', '*', '@', '\', '>', '#', '`' and a digit followed by '.'109 /// or ')' are also considered as indicators.110 bool isHardLineBreakIndicator(llvm::StringRef Rest, bool IsMarkdown) const;111 112 /// Checks if a hard line break should be added after the given line.113 bool isHardLineBreakAfter(llvm::StringRef Line, llvm::StringRef Rest,114 bool IsMarkdown) const;115 116 /// \brief Go through the contents line by line to handle the newlines117 /// and required spacing correctly for markdown rendering.118 ///119 /// Newlines are added if:120 /// - the line ends with punctuation that indicates a line break (.:,;!?)121 /// - the next line starts with a hard line break indicator \\ (escaped122 /// markdown/doxygen command) or @ (doxygen command)123 ///124 /// This newline handling is only used when the client requests markdown125 /// for hover/signature help content.126 /// Markdown does not add any newlines inside paragraphs unless the user127 /// explicitly adds them. For hover/signature help content, we still want to128 /// add newlines in some cases to improve readability, especially when doxygen129 /// parsing is disabled or not implemented (like for signature help).130 /// Therefore we add newlines in the above mentioned cases.131 ///132 /// In addition to that, we need to consider that the user can configure133 /// clangd to treat documentation comments as plain text, while the client134 /// requests markdown.135 /// In this case, all markdown syntax is escaped and will136 /// not be rendered as expected by markdown.137 /// Examples are lists starting with '-' or headings starting with '#'.138 /// With the above next line heuristics, these cases are also covered by the139 /// '\\' new line indicator.140 ///141 /// FIXME: The heuristic fails e.g. for lists starting with '*' because it is142 /// also used for emphasis in markdown and should not be treated as a newline.143 ///144 /// \param OS The stream to render to.145 /// \param ParagraphText The text of the paragraph to render.146 void renderNewlinesMarkdown(llvm::raw_ostream &OS,147 llvm::StringRef ParagraphText) const;148 149 /// \brief Go through the contents line by line to handle the newlines150 /// and required spacing correctly for plain text rendering.151 ///152 /// Newlines are added if:153 /// - the line ends with 2 spaces and a newline follows154 /// - the line ends with punctuation that indicates a line break (.:,;!?)155 /// - the next line starts with a hard line break indicator (-@>#`\\ or a156 /// digit followed by '.' or ')'), ignoring leading whitespace.157 ///158 /// Otherwise, newlines in the input are replaced with a single space.159 ///160 /// Multiple spaces are collapsed into a single space.161 ///162 /// Lines containing only whitespace are ignored.163 ///164 /// This newline handling is only used when the client requests plain165 /// text for hover/signature help content.166 /// Therefore with this approach we mimic the behavior of markdown rendering167 /// for these clients.168 ///169 /// \param OS The stream to render to.170 /// \param ParagraphText The text of the paragraph to render.171 void renderNewlinesPlaintext(llvm::raw_ostream &OS,172 llvm::StringRef ParagraphText) const;173};174 175/// Represents a sequence of one or more documents. Knows how to print them in a176/// list like format, e.g. by prepending with "- " and indentation.177class BulletList : public Block {178public:179 BulletList();180 ~BulletList();181 182 // A BulletList rendered in markdown is a tight list if it is not a nested183 // list and no item contains multiple paragraphs. Otherwise, it is a loose184 // list.185 void renderEscapedMarkdown(llvm::raw_ostream &OS) const override;186 void renderMarkdown(llvm::raw_ostream &OS) const override;187 void renderPlainText(llvm::raw_ostream &OS) const override;188 std::unique_ptr<Block> clone() const override;189 190 class Document &addItem();191 192private:193 std::vector<class Document> Items;194};195 196/// A format-agnostic representation for structured text. Allows rendering into197/// markdown and plaintext.198class Document {199public:200 Document() = default;201 Document(const Document &Other) { *this = Other; }202 Document &operator=(const Document &);203 Document(Document &&) = default;204 Document &operator=(Document &&) = default;205 206 void append(Document Other);207 208 /// Adds a semantical block that will be separate from others.209 Paragraph &addParagraph();210 /// Inserts a horizontal separator to the document.211 void addRuler();212 /// Adds a block of code. This translates to a ``` block in markdown. In plain213 /// text representation, the code block will be surrounded by newlines.214 void addCodeBlock(std::string Code, std::string Language = "cpp");215 /// Heading is a special type of paragraph that will be prepended with \p216 /// Level many '#'s in markdown.217 Paragraph &addHeading(size_t Level);218 219 BulletList &addBulletList();220 221 /// Doesn't contain any trailing newlines and escaped markdown syntax.222 /// It is expected that the result of this function223 /// is rendered as markdown.224 std::string asEscapedMarkdown() const;225 /// Doesn't contain any trailing newlines.226 /// It is expected that the result of this function227 /// is rendered as markdown.228 std::string asMarkdown() const;229 /// Doesn't contain any trailing newlines.230 std::string asPlainText() const;231 232private:233 std::vector<std::unique_ptr<Block>> Children;234};235} // namespace markup236} // namespace clangd237} // namespace clang238 239#endif240