typst/src/syntax/node.rs

199 lines
6.1 KiB
Rust

use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
/// Strong text was enabled / disabled.
Strong,
/// Emphasized text was enabled / disabled.
Emph,
/// Whitespace containing less than two newlines.
Space,
/// A forced line break.
Linebreak,
/// A paragraph break.
Parbreak,
/// Plain text.
Text(String),
/// 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::Strong => p.push('*'),
Self::Emph => p.push('_'),
Self::Space => p.push(' '),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
Self::Text(text) => p.push_str(&text),
Self::Heading(heading) => heading.pretty(p),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => {
if let Expr::Call(call) = expr {
// Format function templates appropriately.
pretty_func_template(call, p, false)
} else {
expr.pretty(p);
}
}
}
}
}
/// A section heading: `= Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
/// The section depth (numer of equals signs 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('=');
}
self.contents.pretty(p);
}
}
/// A raw block with optional syntax highlighting: `` `raw` ``.
///
/// Raw blocks start with 1 or 3+ backticks and end with the same number of
/// backticks.
///
/// When using at least three 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 three backticks.
/// ````typst
/// ```rust println!("hello!")```;
/// ````
/// - Blocks can span multiple lines.
/// ````typst
/// ```rust
/// loop {
/// find_yak().shave();
/// }
/// ```
/// ````
/// - Start with a space to omit the language tag (the space will be trimmed
/// from the output).
/// `````typst
/// ```` This has no leading space.````
/// `````
/// - Use more backticks to allow backticks in the raw text.
/// `````typst
/// ```` This contains ```backticks```.````
/// `````
///
/// # Trimming
/// If we would always render the raw text between the backticks exactly as
/// given, some things would become cumbersome/impossible to write:
/// - Typical multiline code blocks (like in the example above) would have an
/// additional newline before and after the code.
/// - Multi-line blocks would need to start with a space since a word would be
/// interpreted as a language tag.
/// - Text ending with a backtick would be impossible since the backtick would
/// be interpreted as belonging to the closing backticks.
///
/// To fix these problems, we sometimes trim a bit of space from blocks with 3+
/// backticks:
/// - At the start, we trim a single space or a sequence of whitespace followed
/// by a newline.
/// - At the end, we trim
/// - a single space if the raw text ends with a backtick followed only by
/// whitespace,
/// - a newline followed by a sequence of whitespace.
///
/// You can thus produce a single backtick without surrounding spaces with the
/// sequence ```` ``` ` ``` ````.
///
/// Note that with these rules 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 is block-level, that is, it has 3+ backticks
/// and contains at least one newline.
pub block: bool,
}
impl Pretty for NodeRaw {
fn pretty(&self, p: &mut Printer) {
// Find out how many backticks we need.
let mut backticks = 1;
// Language tag and block-level are only possible with 3+ backticks.
if self.lang.is_some() || self.block {
backticks = 3;
}
// More backticks may be required if there are lots of consecutive
// backticks in the lines.
let mut count = 0;
for line in &self.lines {
for c in line.chars() {
if c == '`' {
count += 1;
backticks = backticks.max(3).max(count + 1);
} else {
count = 0;
}
}
}
// Starting backticks.
for _ in 0 .. backticks {
p.push('`');
}
// Language tag.
if let Some(lang) = &self.lang {
lang.pretty(p);
}
// Start untrimming.
if self.block {
p.push('\n');
} else if backticks >= 3 {
p.push(' ');
}
// The lines.
p.join(&self.lines, "\n", |line, p| p.push_str(line));
// End untrimming.
if self.block {
p.push('\n');
} else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) {
p.push(' ');
}
// Ending backticks.
for _ in 0 .. backticks {
p.push('`');
}
}
}