-
Notifications
You must be signed in to change notification settings - Fork 917
/
Copy pathmarkdown.rs
248 lines (236 loc) · 7.41 KB
/
markdown.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Write Markdown to the terminal
use std::io::Write;
use pulldown_cmark::{Event, Tag, TagEnd};
use crate::process::terminalsource::{Attr, Color, ColorableTerminal};
// Handles the wrapping of text written to the console
struct LineWrapper<'a> {
indent: u32,
margin: u32,
pos: u32,
w: &'a mut ColorableTerminal,
}
impl<'a> LineWrapper<'a> {
// Just write a newline
fn write_line(&mut self) {
let _ = writeln!(self.w.lock());
// Reset column position to start of line
self.pos = 0;
}
// Called before writing text to ensure indent is applied
fn write_indent(&mut self) {
if self.pos == 0 {
// Write a space for each level of indent
for _ in 0..self.indent {
let _ = write!(self.w.lock(), " ");
}
self.pos = self.indent;
}
}
// Write a non-breaking word
fn write_word(&mut self, word: &str) {
// Ensure correct indentation
self.write_indent();
let word_len = word.len() as u32;
// If this word goes past the margin
if self.pos + word_len > self.margin {
// And adding a newline would give us more space
if self.pos > self.indent {
// Then add a newline!
self.write_line();
self.write_indent();
}
}
// Write the word
let _ = write!(self.w.lock(), "{word}");
self.pos += word_len;
}
fn write_space(&mut self) {
if self.pos > self.indent {
if self.pos < self.margin {
self.write_word(" ");
} else {
self.write_line();
}
}
}
// Writes a span of text which wraps at the margin
fn write_span(&mut self, text: &str) {
// Allow words to wrap on whitespace
let mut is_first = true;
for word in text.split(char::is_whitespace) {
if is_first {
is_first = false;
} else {
self.write_space();
}
self.write_word(word);
}
}
// Writes code block where each line is indented
fn write_code_block(&mut self, text: &str) {
for line in text.lines() {
self.write_word(line); // Will call write_indent()
self.write_line();
}
}
// Constructor
fn new(w: &'a mut ColorableTerminal, indent: u32, margin: u32) -> Self {
LineWrapper {
indent,
margin,
pos: indent,
w,
}
}
}
// Handles the formatting of text
struct LineFormatter<'a> {
is_code_block: bool,
wrapper: LineWrapper<'a>,
attrs: Vec<Attr>,
}
impl<'a> LineFormatter<'a> {
fn new(w: &'a mut ColorableTerminal, indent: u32, margin: u32) -> Self {
LineFormatter {
is_code_block: false,
wrapper: LineWrapper::new(w, indent, margin),
attrs: Vec::new(),
}
}
fn push_attr(&mut self, attr: Attr) {
self.attrs.push(attr);
let _ = self.wrapper.w.attr(attr);
}
fn pop_attr(&mut self) {
self.attrs.pop();
let _ = self.wrapper.w.reset();
for attr in &self.attrs {
let _ = self.wrapper.w.attr(*attr);
}
}
fn start_tag(&mut self, tag: Tag<'a>) {
match tag {
Tag::Paragraph => {
self.wrapper.write_line();
}
Tag::Heading { .. } => {
self.push_attr(Attr::Bold);
self.wrapper.write_line();
}
Tag::MetadataBlock(_) => {}
Tag::Table(_alignments) => {}
Tag::TableHead => {}
Tag::TableRow => {}
Tag::TableCell => {}
Tag::BlockQuote(_) => {}
Tag::DefinitionList => {
self.wrapper.write_line();
self.wrapper.indent += 2;
}
Tag::DefinitionListTitle | Tag::DefinitionListDefinition => {}
Tag::CodeBlock(_) | Tag::HtmlBlock { .. } => {
self.wrapper.write_line();
self.wrapper.indent += 2;
self.is_code_block = true;
}
Tag::List(_) => {
self.wrapper.write_line();
self.wrapper.indent += 2;
}
Tag::Item => {
self.wrapper.write_line();
}
Tag::Emphasis => {
self.push_attr(Attr::ForegroundColor(Color::Red));
}
Tag::Strong => {}
Tag::Strikethrough => {}
Tag::Link { .. } => {}
Tag::Image { .. } => {}
Tag::FootnoteDefinition(_name) => {}
Tag::Superscript => {}
Tag::Subscript => {}
}
}
fn end_tag(&mut self, tag: TagEnd) {
match tag {
TagEnd::Paragraph => {
self.wrapper.write_line();
}
TagEnd::Heading { .. } => {
self.wrapper.write_line();
self.pop_attr();
}
TagEnd::Table => {}
TagEnd::TableHead => {}
TagEnd::TableRow => {}
TagEnd::TableCell => {}
TagEnd::BlockQuote(_) => {}
TagEnd::DefinitionList => {
self.wrapper.indent -= 2;
self.wrapper.write_line();
}
TagEnd::DefinitionListTitle | TagEnd::DefinitionListDefinition => {}
TagEnd::CodeBlock | TagEnd::HtmlBlock => {
self.is_code_block = false;
self.wrapper.indent -= 2;
}
TagEnd::List(_) => {
self.wrapper.indent -= 2;
self.wrapper.write_line();
}
TagEnd::Item => {}
TagEnd::Emphasis => {
self.pop_attr();
}
TagEnd::Strong => {}
TagEnd::Strikethrough => {}
TagEnd::Link => {}
TagEnd::Image => {} // shouldn't happen, handled in start
TagEnd::FootnoteDefinition => {}
TagEnd::MetadataBlock(_) => {}
TagEnd::Superscript => {}
TagEnd::Subscript => {}
}
}
fn process_event(&mut self, event: Event<'a>) {
use self::Event::*;
match event {
Start(tag) => self.start_tag(tag),
End(tag) => self.end_tag(tag),
Text(text) => {
if self.is_code_block {
self.wrapper.write_code_block(&text);
} else {
self.wrapper.write_span(&text);
}
}
Code(code) => {
self.push_attr(Attr::Bold);
self.wrapper.write_word(&code);
self.pop_attr();
}
Html(_html) => {}
SoftBreak => {
self.wrapper.write_line();
}
HardBreak => {
self.wrapper.write_line();
}
Rule => {}
FootnoteReference(_name) => {}
TaskListMarker(true) => {}
TaskListMarker(false) => {}
InlineHtml(_) => {}
InlineMath(_) => {}
DisplayMath(_) => {}
}
}
}
pub(crate) fn md<S: AsRef<str>>(t: &mut ColorableTerminal, content: S) {
let mut f = LineFormatter::new(t, 0, 79);
let parser = pulldown_cmark::Parser::new(content.as_ref());
for event in parser {
f.process_event(event);
}
}