clang-tools  9.0.0
FormattedString.cpp
Go to the documentation of this file.
1 //===--- FormattedString.cpp --------------------------------*- 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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "FormattedString.h"
9 #include "clang/Basic/CharInfo.h"
10 #include "llvm/ADT/StringRef.h"
11 #include "llvm/Support/ErrorHandling.h"
12 #include "llvm/Support/FormatVariadic.h"
13 #include <cstddef>
14 #include <string>
15 
16 namespace clang {
17 namespace clangd {
18 
19 namespace {
20 /// Escape a markdown text block. Ensures the punctuation will not introduce
21 /// any of the markdown constructs.
22 static std::string renderText(llvm::StringRef Input) {
23  // Escaping ASCII punctiation ensures we can't start a markdown construct.
24  constexpr llvm::StringLiteral Punctuation =
25  R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
26 
27  std::string R;
28  for (size_t From = 0; From < Input.size();) {
29  size_t Next = Input.find_first_of(Punctuation, From);
30  R += Input.substr(From, Next - From);
31  if (Next == llvm::StringRef::npos)
32  break;
33  R += "\\";
34  R += Input[Next];
35 
36  From = Next + 1;
37  }
38  return R;
39 }
40 
41 /// Renders \p Input as an inline block of code in markdown. The returned value
42 /// is surrounded by backticks and the inner contents are properly escaped.
43 static std::string renderInlineBlock(llvm::StringRef Input) {
44  std::string R;
45  // Double all backticks to make sure we don't close the inline block early.
46  for (size_t From = 0; From < Input.size();) {
47  size_t Next = Input.find("`", From);
48  R += Input.substr(From, Next - From);
49  if (Next == llvm::StringRef::npos)
50  break;
51  R += "``"; // double the found backtick.
52 
53  From = Next + 1;
54  }
55  // If results starts with a backtick, add spaces on both sides. The spaces
56  // are ignored by markdown renderers.
57  if (llvm::StringRef(R).startswith("`") || llvm::StringRef(R).endswith("`"))
58  return "` " + std::move(R) + " `";
59  // Markdown render should ignore first and last space if both are there. We
60  // add an extra pair of spaces in that case to make sure we render what the
61  // user intended.
62  if (llvm::StringRef(R).startswith(" ") && llvm::StringRef(R).endswith(" "))
63  return "` " + std::move(R) + " `";
64  return "`" + std::move(R) + "`";
65 }
66 /// Render \p Input as markdown code block with a specified \p Language. The
67 /// result is surrounded by >= 3 backticks. Although markdown also allows to use
68 /// '~' for code blocks, they are never used.
69 static std::string renderCodeBlock(llvm::StringRef Input,
70  llvm::StringRef Language) {
71  // Count the maximum number of consecutive backticks in \p Input. We need to
72  // start and end the code block with more.
73  unsigned MaxBackticks = 0;
74  unsigned Backticks = 0;
75  for (char C : Input) {
76  if (C == '`') {
77  ++Backticks;
78  continue;
79  }
80  MaxBackticks = std::max(MaxBackticks, Backticks);
81  Backticks = 0;
82  }
83  MaxBackticks = std::max(Backticks, MaxBackticks);
84  // Use the corresponding number of backticks to start and end a code block.
85  std::string BlockMarker(/*Repeat=*/std::max(3u, MaxBackticks + 1), '`');
86  return BlockMarker + Language.str() + "\n" + Input.str() + "\n" + BlockMarker;
87 }
88 
89 } // namespace
90 
91 void FormattedString::appendText(std::string Text) {
92  Chunk C;
93  C.Kind = ChunkKind::PlainText;
94  C.Contents = Text;
95  Chunks.push_back(C);
96 }
97 
98 void FormattedString::appendCodeBlock(std::string Code, std::string Language) {
99  Chunk C;
100  C.Kind = ChunkKind::CodeBlock;
101  C.Contents = std::move(Code);
102  C.Language = std::move(Language);
103  Chunks.push_back(std::move(C));
104 }
105 
106 void FormattedString::appendInlineCode(std::string Code) {
107  Chunk C;
108  C.Kind = ChunkKind::InlineCodeBlock;
109  C.Contents = std::move(Code);
110  Chunks.push_back(std::move(C));
111 }
112 
113 std::string FormattedString::renderAsMarkdown() const {
114  std::string R;
115  auto EnsureWhitespace = [&R]() {
116  // Adds a space for nicer rendering.
117  if (!R.empty() && !isWhitespace(R.back()))
118  R += " ";
119  };
120  for (const auto &C : Chunks) {
121  switch (C.Kind) {
122  case ChunkKind::PlainText:
123  if (!C.Contents.empty() && !isWhitespace(C.Contents.front()))
124  EnsureWhitespace();
125  R += renderText(C.Contents);
126  continue;
127  case ChunkKind::InlineCodeBlock:
128  EnsureWhitespace();
129  R += renderInlineBlock(C.Contents);
130  continue;
131  case ChunkKind::CodeBlock:
132  if (!R.empty() && !llvm::StringRef(R).endswith("\n"))
133  R += "\n";
134  R += renderCodeBlock(C.Contents, C.Language);
135  R += "\n";
136  continue;
137  }
138  llvm_unreachable("unhanlded ChunkKind");
139  }
140  return R;
141 }
142 
143 std::string FormattedString::renderAsPlainText() const {
144  std::string R;
145  auto EnsureWhitespace = [&]() {
146  if (R.empty() || isWhitespace(R.back()))
147  return;
148  R += " ";
149  };
150  Optional<bool> LastWasBlock;
151  for (const auto &C : Chunks) {
152  bool IsBlock = C.Kind == ChunkKind::CodeBlock;
153  if (LastWasBlock.hasValue() && (IsBlock || *LastWasBlock))
154  R += "\n\n";
155  LastWasBlock = IsBlock;
156 
157  switch (C.Kind) {
158  case ChunkKind::PlainText:
159  EnsureWhitespace();
160  R += C.Contents;
161  break;
162  case ChunkKind::InlineCodeBlock:
163  EnsureWhitespace();
164  R += C.Contents;
165  break;
166  case ChunkKind::CodeBlock:
167  R += C.Contents;
168  break;
169  }
170  // Trim trailing whitespace in chunk.
171  while (!R.empty() && isWhitespace(R.back()))
172  R.pop_back();
173  }
174  return R;
175 }
176 
177 std::string FormattedString::renderForTests() const {
178  std::string R;
179  for (const auto &C : Chunks) {
180  switch (C.Kind) {
181  case ChunkKind::PlainText:
182  R += "text[" + C.Contents + "]";
183  break;
184  case ChunkKind::InlineCodeBlock:
185  R += "code[" + C.Contents + "]";
186  break;
187  case ChunkKind::CodeBlock:
188  if (!R.empty())
189  R += "\n";
190  R += llvm::formatv("codeblock({0}) [\n{1}\n]\n", C.Language, C.Contents);
191  break;
192  }
193  }
194  while (!R.empty() && isWhitespace(R.back()))
195  R.pop_back();
196  return R;
197 }
198 } // namespace clangd
199 } // namespace clang
std::string renderForTests() const
void appendText(std::string Text)
Append plain text to the end of the string.
std::string renderAsMarkdown() const
void appendInlineCode(std::string Code)
Append an inline block of C++ code.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::string renderAsPlainText() const
void appendCodeBlock(std::string Code, std::string Language="cpp")
Append a block of C++ code.