Skip to content

Commit d68089d

Browse files
committed
[Diagnostics] Add a basic terminal markdown printer for educational notes
Currently the printer only supports the subset of markdown used by educational notes. It bolds headers, improves the appearance of lists and horizontal rules, and does basic indenting of blockquotes and code blocks.
1 parent ab56d0d commit d68089d

File tree

6 files changed

+352
-95
lines changed

6 files changed

+352
-95
lines changed

include/swift/Markup/Markup.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class MarkupContext final {
6060
LineList getLineList(swift::RawComment RC);
6161
};
6262

63+
Document *parseDocument(MarkupContext &MC, StringRef String);
6364
Document *parseDocument(MarkupContext &MC, LineList &LL);
6465

6566
} // namespace markup

lib/Frontend/PrintingDiagnosticConsumer.cpp

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/AST/DiagnosticEngine.h"
1919
#include "swift/Basic/LLVM.h"
2020
#include "swift/Basic/SourceManager.h"
21+
#include "swift/Markup/Markup.h"
2122
#include "llvm/ADT/SmallString.h"
2223
#include "llvm/ADT/StringRef.h"
2324
#include "llvm/ADT/Twine.h"
@@ -28,6 +29,7 @@
2829
#include <algorithm>
2930

3031
using namespace swift;
32+
using namespace swift::markup;
3133

3234
namespace {
3335
class ColoredStream : public raw_ostream {
@@ -85,6 +87,190 @@ namespace {
8587
size_t preferred_buffer_size() const override { return 0; }
8688
};
8789

90+
// MARK: Markdown Printing
91+
class TerminalMarkupPrinter : public MarkupASTVisitor<TerminalMarkupPrinter> {
92+
llvm::raw_ostream &OS;
93+
unsigned Indent;
94+
unsigned ShouldBold;
95+
96+
void indent(unsigned Amount = 2) { Indent += Amount; }
97+
98+
void dedent(unsigned Amount = 2) {
99+
assert(Indent >= Amount && "dedent without matching indent");
100+
Indent -= Amount;
101+
}
102+
103+
void bold() {
104+
++ShouldBold;
105+
updateFormatting();
106+
}
107+
108+
void unbold() {
109+
assert(ShouldBold > 0 && "unbolded without matching bold");
110+
--ShouldBold;
111+
updateFormatting();
112+
}
113+
114+
void updateFormatting() {
115+
OS.resetColor();
116+
if (ShouldBold > 0)
117+
OS.changeColor(raw_ostream::Colors::SAVEDCOLOR, true);
118+
}
119+
120+
void print(StringRef Str) {
121+
for (auto c : Str) {
122+
OS << c;
123+
if (c == '\n')
124+
for (unsigned i = 0; i < Indent; ++i)
125+
OS << ' ';
126+
}
127+
}
128+
129+
public:
130+
TerminalMarkupPrinter(llvm::raw_ostream &OS)
131+
: OS(OS), Indent(0), ShouldBold(0) {}
132+
133+
void printNewline() { print("\n"); }
134+
135+
void visitDocument(const Document *D) {
136+
for (const auto *Child : D->getChildren()) {
137+
if (Child->getKind() == ASTNodeKind::Paragraph) {
138+
// Add a newline before top-level paragraphs
139+
printNewline();
140+
}
141+
visit(Child);
142+
}
143+
}
144+
145+
void visitBlockQuote(const BlockQuote *BQ) {
146+
indent();
147+
printNewline();
148+
for (const auto *Child : BQ->getChildren())
149+
visit(Child);
150+
dedent();
151+
}
152+
153+
void visitList(const List *BL) {
154+
indent();
155+
printNewline();
156+
for (const auto *Child : BL->getChildren())
157+
visit(Child);
158+
dedent();
159+
}
160+
161+
void visitItem(const Item *I) {
162+
print("- ");
163+
for (const auto *N : I->getChildren())
164+
visit(N);
165+
}
166+
167+
void visitCodeBlock(const CodeBlock *CB) {
168+
indent();
169+
printNewline();
170+
print(CB->getLiteralContent());
171+
dedent();
172+
}
173+
174+
void visitCode(const Code *C) {
175+
print("'");
176+
print(C->getLiteralContent());
177+
print("'");
178+
}
179+
180+
void visitHTML(const HTML *H) { print(H->getLiteralContent()); }
181+
182+
void visitInlineHTML(const InlineHTML *IH) {
183+
print(IH->getLiteralContent());
184+
}
185+
186+
void visitSoftBreak(const SoftBreak *SB) { printNewline(); }
187+
188+
void visitLineBreak(const LineBreak *LB) {
189+
printNewline();
190+
printNewline();
191+
}
192+
193+
void visitLink(const Link *L) {
194+
print("[");
195+
for (const auto *Child : L->getChildren())
196+
visit(Child);
197+
print("](");
198+
print(L->getDestination());
199+
print(")");
200+
}
201+
202+
void visitImage(const Image *I) { llvm_unreachable("unsupported"); }
203+
204+
void visitParagraph(const Paragraph *P) {
205+
for (const auto *Child : P->getChildren())
206+
visit(Child);
207+
printNewline();
208+
}
209+
210+
// TODO: add raw_ostream support for italics ANSI codes in LLVM.
211+
void visitEmphasis(const Emphasis *E) {
212+
for (const auto *Child : E->getChildren())
213+
visit(Child);
214+
}
215+
216+
void visitStrong(const Strong *E) {
217+
bold();
218+
for (const auto *Child : E->getChildren())
219+
visit(Child);
220+
unbold();
221+
}
222+
223+
void visitHRule(const HRule *HR) {
224+
print("--------------");
225+
printNewline();
226+
}
227+
228+
void visitHeader(const Header *H) {
229+
bold();
230+
for (const auto *Child : H->getChildren())
231+
visit(Child);
232+
unbold();
233+
printNewline();
234+
}
235+
236+
void visitText(const Text *T) { print(T->getLiteralContent()); }
237+
238+
void visitPrivateExtension(const PrivateExtension *PE) {
239+
llvm_unreachable("unsupported");
240+
}
241+
242+
void visitParamField(const ParamField *PF) {
243+
llvm_unreachable("unsupported");
244+
}
245+
246+
void visitReturnField(const ReturnsField *RF) {
247+
llvm_unreachable("unsupported");
248+
}
249+
250+
void visitThrowField(const ThrowsField *TF) {
251+
llvm_unreachable("unsupported");
252+
}
253+
254+
#define MARKUP_SIMPLE_FIELD(Id, Keyword, XMLKind) \
255+
void visit##Id(const Id *Field) { llvm_unreachable("unsupported"); }
256+
#include "swift/Markup/SimpleFields.def"
257+
};
258+
259+
static void printMarkdown(StringRef Content, raw_ostream &Out,
260+
bool UseColor) {
261+
markup::MarkupContext ctx;
262+
auto document = markup::parseDocument(ctx, Content);
263+
if (UseColor) {
264+
ColoredStream stream{Out};
265+
TerminalMarkupPrinter printer(stream);
266+
printer.visit(document);
267+
} else {
268+
NoColorStream stream{Out};
269+
TerminalMarkupPrinter printer(stream);
270+
printer.visit(document);
271+
}
272+
}
273+
88274
// MARK: Experimental diagnostic printing.
89275

90276
static void printDiagnosticKind(DiagnosticKind kind, raw_ostream &out) {
@@ -716,8 +902,10 @@ void PrintingDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
716902
printDiagnostic(SM, Info);
717903

718904
for (auto path : Info.EducationalNotePaths) {
719-
if (auto buffer = SM.getFileSystem()->getBufferForFile(path))
720-
Stream << buffer->get()->getBuffer() << "\n";
905+
if (auto buffer = SM.getFileSystem()->getBufferForFile(path)) {
906+
printMarkdown(buffer->get()->getBuffer(), Stream, ForceColors);
907+
Stream << "\n";
908+
}
721909
}
722910

723911
for (auto ChildInfo : Info.ChildDiagnosticInfo) {

0 commit comments

Comments
 (0)