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
|
use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
/// Plain text.
Text(String),
/// Whitespace containing less than two newlines.
Space,
/// A forced line break.
Linebreak,
/// A paragraph break.
Parbreak,
/// Strong text was enabled / disabled.
Strong,
/// Emphasized text was enabled / disabled.
Emph,
/// A section heading.
Heading(NodeHeading),
/// An optionally syntax-highlighted raw block.
Raw(NodeRaw),
/// An expression.
Expr(Expr),
}
impl Pretty for Node {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Text(text) => p.push_str(&text),
Self::Space => p.push_str(" "),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
Self::Strong => p.push_str("*"),
Self::Emph => p.push_str("_"),
Self::Heading(heading) => heading.pretty(p),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => pretty_expr_node(expr, p),
}
}
}
/// Pretty print an expression in a node context.
pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) {
match expr {
// Prefer bracket calls over expression blocks with just a single paren
// call.
//
// Example: Transforms "{v()}" => "[v]".
Expr::Call(call) => pretty_bracket_call(call, p, false),
// Remove unncessary nesting of content and expression blocks.
//
// Example: Transforms "{{Hi}}" => "Hi".
Expr::Content(content) => content.pretty(p),
_ => {
p.push_str("{");
expr.pretty(p);
p.push_str("}");
}
}
}
/// A section heading: `# Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
/// The section depth (numer of hashtags minus 1, capped at 5).
pub level: Spanned<u8>,
/// The contents of the heading.
pub contents: Tree,
}
impl Pretty for NodeHeading {
fn pretty(&self, p: &mut Printer) {
for _ in 0 ..= self.level.v {
p.push_str("#");
}
self.contents.pretty(p);
}
}
/// A raw block with optional syntax highlighting: `` `raw` ``.
///
/// Raw blocks start with an arbitrary number of backticks and end with the same
/// number of backticks. If you want to include a sequence of backticks in a raw
/// block, simply surround the block with more backticks.
///
/// When using at least two backticks, an optional language tag may follow
/// directly after the backticks. This tag defines which language to
/// syntax-highlight the text in. Apart from the language tag and some
/// whitespace trimming discussed below, everything inside a raw block is
/// rendered verbatim, in particular, there are no escape sequences.
///
/// # Examples
/// - Raw text is surrounded by backticks.
/// ```typst
/// `raw`
/// ```
/// - An optional language tag may follow directly at the start when the block
/// is surrounded by at least two backticks.
/// ```typst
/// ``rust println!("hello!")``;
/// ```
/// - Blocks can span multiple lines. Two backticks suffice to be able to
/// specify the language tag, but three are fine, too.
/// ```typst
/// ``rust
/// loop {
/// find_yak().shave();
/// }
/// ``
/// ```
/// - Start with a space to omit the language tag (the space will be trimmed
/// from the output) and use more backticks to allow backticks in the raw
/// text.
/// `````typst
/// ```` This contains ```backticks``` and has no leading & trailing spaces. ````
/// `````
///
/// # Trimming
/// If we would always render the raw text between the backticks exactly as
/// given, a few things would become problematic or even impossible:
/// - Typical multiline code blocks (like in the example above) would have an
/// additional newline before and after the code.
/// - Raw text wrapped in more than one backtick could not exist without
/// leading whitespace since the first word would be interpreted as a
/// language tag.
/// - A single backtick without surrounding spaces could not exist as raw text
/// since it would be interpreted as belonging to the opening or closing
/// backticks.
///
/// To fix these problems, we trim text in multi-backtick blocks as follows:
/// - We trim a single space or a sequence of whitespace followed by a newline
/// at the start.
/// - We trim a single space or a newline followed by a sequence of whitespace
/// at the end.
///
/// With these rules, a single raw backtick can be produced by the sequence
/// ``` `` ` `` ```, ``` `` unhighlighted text `` ``` has no surrounding
/// spaces and multiline code blocks don't have extra empty lines. Note that
/// you can always force leading or trailing whitespace simply by adding more
/// spaces.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeRaw {
/// An optional identifier specifying the language to syntax-highlight in.
pub lang: Option<Ident>,
/// The lines of raw text, determined as the raw string between the
/// backticks trimmed according to the above rules and split at newlines.
pub lines: Vec<String>,
/// Whether the element can be layouted inline.
///
/// - When true, it will be layouted integrated within the surrounding
/// paragraph.
/// - When false, it will be separated into its own paragraph.
///
/// Single-backtick blocks are always inline-level. Multi-backtick blocks
/// are inline-level when they contain no newlines.
pub inline: bool,
}
impl Pretty for NodeRaw {
fn pretty(&self, p: &mut Printer) {
p.push_str("`");
if let Some(lang) = &self.lang {
p.push_str(&lang);
p.push_str(" ");
}
// TODO: Technically, we should handle backticks in the lines
// by wrapping with more backticks and possibly adding space
// before the first or after the last line.
p.join(&self.lines, "\n", |line, p| p.push_str(line));
p.push_str("`");
}
}
#[cfg(test)]
mod tests {
use super::super::tests::test_pretty;
#[test]
fn test_pretty_print_removes_nesting() {
// Even levels of nesting do not matter.
test_pretty("{{Hi}}", "Hi");
test_pretty("{{{{Hi}}}}", "Hi");
}
#[test]
fn test_pretty_print_prefers_bracket_calls() {
// All reduces to a simple bracket call.
test_pretty("{v()}", "[v]");
test_pretty("[v]", "[v]");
test_pretty("{[v]}", "[v]");
test_pretty("{{[v]}}", "[v]");
}
#[test]
fn test_pretty_print_nodes() {
// Basic text and markup.
test_pretty(r"*Hi_\", r"*Hi_\");
// Whitespace.
test_pretty(" ", " ");
test_pretty("\n\n\n", "\n\n");
// Heading and raw.
test_pretty("# Ok", "# Ok");
test_pretty("``\none\ntwo\n``", "`one\ntwo`");
test_pretty("`lang one\ntwo`", "`lang one\ntwo`");
}
}
|