mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Tidy up raw blocks 🧹
- Better trimming (only trim at the end if necessary) - Fixed block-level layouting - Improved pretty printing - Flip inline variable to block - Flip inline variable to display for math formulas
This commit is contained in:
parent
6fcef9973b
commit
d86a5e8a1f
@ -142,6 +142,10 @@ impl Eval for Spanned<&NodeRaw> {
|
||||
}));
|
||||
}
|
||||
|
||||
if self.v.block {
|
||||
ctx.apply_parbreak();
|
||||
}
|
||||
|
||||
ctx.push(NodeStack {
|
||||
dirs: ctx.state.dirs,
|
||||
align: ctx.state.align,
|
||||
@ -149,6 +153,10 @@ impl Eval for Spanned<&NodeRaw> {
|
||||
children,
|
||||
});
|
||||
|
||||
if self.v.block {
|
||||
ctx.apply_parbreak();
|
||||
}
|
||||
|
||||
ctx.state.font.families = prev;
|
||||
}
|
||||
}
|
||||
@ -466,8 +474,8 @@ impl Eval for Spanned<&ExprFor> {
|
||||
iterate!(for (k => key, v => value) in dict.into_iter())
|
||||
}
|
||||
|
||||
(ForPattern::KeyValue(..), Value::Str(_))
|
||||
| (ForPattern::KeyValue(..), Value::Array(_)) => {
|
||||
(ForPattern::KeyValue(_, _), Value::Str(_))
|
||||
| (ForPattern::KeyValue(_, _), Value::Array(_)) => {
|
||||
ctx.diag(error!(self.v.pat.span, "mismatched pattern"));
|
||||
Value::Error
|
||||
}
|
||||
|
@ -110,15 +110,15 @@ impl Pretty for Value {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Value::None => p.push_str("none"),
|
||||
Value::Bool(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Int(v) => p.push_str(itoa::Buffer::new().format(*v)),
|
||||
Value::Float(v) => p.push_str(ryu::Buffer::new().format(*v)),
|
||||
Value::Length(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Angle(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Relative(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Linear(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Color(v) => write!(p, "{}", v).unwrap(),
|
||||
Value::Str(v) => write!(p, "{:?}", v).unwrap(),
|
||||
Value::Bool(v) => v.pretty(p),
|
||||
Value::Int(v) => v.pretty(p),
|
||||
Value::Float(v) => v.pretty(p),
|
||||
Value::Length(v) => v.pretty(p),
|
||||
Value::Angle(v) => v.pretty(p),
|
||||
Value::Relative(v) => v.pretty(p),
|
||||
Value::Linear(v) => v.pretty(p),
|
||||
Value::Color(v) => v.pretty(p),
|
||||
Value::Str(v) => v.pretty(p),
|
||||
Value::Array(v) => v.pretty(p),
|
||||
Value::Dict(v) => v.pretty(p),
|
||||
Value::Template(v) => pretty_template(v, p),
|
||||
@ -134,12 +134,12 @@ pub type ValueArray = Vec<Value>;
|
||||
|
||||
impl Pretty for ValueArray {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
p.join(self, ", ", |item, p| item.pretty(p));
|
||||
if self.len() == 1 {
|
||||
p.push_str(",");
|
||||
p.push(',');
|
||||
}
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,9 +148,9 @@ pub type ValueDict = BTreeMap<String, Value>;
|
||||
|
||||
impl Pretty for ValueDict {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
if self.is_empty() {
|
||||
p.push_str(":");
|
||||
p.push(':');
|
||||
} else {
|
||||
p.join(self, ", ", |(key, value), p| {
|
||||
p.push_str(key);
|
||||
@ -158,7 +158,7 @@ impl Pretty for ValueDict {
|
||||
value.pretty(p);
|
||||
});
|
||||
}
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,14 +59,14 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
Token::Underscore => Node::Emph,
|
||||
Token::Eq => {
|
||||
if *at_start {
|
||||
return Some(Node::Heading(heading(p)));
|
||||
return Some(heading(p));
|
||||
} else {
|
||||
Node::Text(p.get(p.peek_span()).into())
|
||||
Node::Text(p.peek_src().into())
|
||||
}
|
||||
}
|
||||
Token::Tilde => Node::Text("\u{00A0}".into()),
|
||||
Token::Backslash => Node::Linebreak,
|
||||
Token::Raw(t) => Node::Raw(raw(p, t)),
|
||||
Token::Raw(t) => raw(p, t),
|
||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||
|
||||
// Keywords.
|
||||
@ -122,7 +122,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
}
|
||||
|
||||
/// Parse a heading.
|
||||
fn heading(p: &mut Parser) -> NodeHeading {
|
||||
fn heading(p: &mut Parser) -> Node {
|
||||
// Count depth.
|
||||
let mut level = p.span(|p| {
|
||||
p.assert(&[Token::Eq]);
|
||||
@ -147,16 +147,16 @@ fn heading(p: &mut Parser) -> NodeHeading {
|
||||
}
|
||||
}
|
||||
|
||||
NodeHeading { level, contents }
|
||||
Node::Heading(NodeHeading { level, contents })
|
||||
}
|
||||
|
||||
/// Handle a raw block.
|
||||
fn raw(p: &mut Parser, token: TokenRaw) -> NodeRaw {
|
||||
fn raw(p: &mut Parser, token: TokenRaw) -> Node {
|
||||
let raw = resolve::resolve_raw(token.text, token.backticks);
|
||||
if !token.terminated {
|
||||
p.diag(error!(p.peek_span().end, "expected backtick(s)"));
|
||||
}
|
||||
raw
|
||||
Node::Raw(raw)
|
||||
}
|
||||
|
||||
/// Handle a unicode escape sequence.
|
||||
|
@ -243,6 +243,11 @@ impl<'s> Parser<'s> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Peek at the source of the next token.
|
||||
pub fn peek_src(&self) -> &'s str {
|
||||
self.get(self.peek_span())
|
||||
}
|
||||
|
||||
/// Checks whether the next token fulfills a condition.
|
||||
///
|
||||
/// Returns `false` if there is no next token.
|
||||
|
@ -54,13 +54,13 @@ pub fn resolve_raw(text: &str, backticks: usize) -> NodeRaw {
|
||||
NodeRaw {
|
||||
lang: Ident::new(tag),
|
||||
lines,
|
||||
inline: !had_newline,
|
||||
block: had_newline,
|
||||
}
|
||||
} else {
|
||||
NodeRaw {
|
||||
lang: None,
|
||||
lines: split_lines(text),
|
||||
inline: true,
|
||||
block: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,10 +77,14 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) {
|
||||
/// Trim raw text and splits it into lines.
|
||||
///
|
||||
/// Returns whether at least one newline was contained in `raw`.
|
||||
fn trim_and_split_raw(raw: &str) -> (Vec<String>, bool) {
|
||||
// Trims one whitespace at end and start.
|
||||
let raw = raw.strip_prefix(' ').unwrap_or(raw);
|
||||
let raw = raw.strip_suffix(' ').unwrap_or(raw);
|
||||
fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
|
||||
// Trims one space at the start.
|
||||
raw = raw.strip_prefix(' ').unwrap_or(raw);
|
||||
|
||||
// Trim one space at the end if the last non-whitespace char is a backtick.
|
||||
if raw.trim_end().ends_with('`') {
|
||||
raw = raw.strip_suffix(' ').unwrap_or(raw);
|
||||
}
|
||||
|
||||
let mut lines = split_lines(raw);
|
||||
let had_newline = lines.len() > 1;
|
||||
@ -167,29 +171,29 @@ mod tests {
|
||||
backticks: usize,
|
||||
lang: Option<&str>,
|
||||
lines: &[&str],
|
||||
inline: bool,
|
||||
block: bool,
|
||||
) {
|
||||
assert_eq!(resolve_raw(raw, backticks), NodeRaw {
|
||||
lang: lang.map(|id| Ident(id.into())),
|
||||
lines: lines.iter().map(ToString::to_string).collect(),
|
||||
inline,
|
||||
block,
|
||||
});
|
||||
}
|
||||
|
||||
// Just one backtick.
|
||||
test("py", 1, None, &["py"], true);
|
||||
test("1\n2", 1, None, &["1", "2"], true);
|
||||
test("1\r\n2", 1, None, &["1", "2"], true);
|
||||
test("py", 1, None, &["py"], false);
|
||||
test("1\n2", 1, None, &["1", "2"], false);
|
||||
test("1\r\n2", 1, None, &["1", "2"], false);
|
||||
|
||||
// More than one backtick with lang tag.
|
||||
test("js alert()", 2, Some("js"), &["alert()"], true);
|
||||
test("py quit(\n\n) ", 3, Some("py"), &["quit(", "", ")"], false);
|
||||
test("♥", 2, None, &[], true);
|
||||
test("js alert()", 2, Some("js"), &["alert()"], false);
|
||||
test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true);
|
||||
test("♥", 2, None, &[], false);
|
||||
|
||||
// Trimming of whitespace (tested more thoroughly in separate test).
|
||||
test(" a", 2, None, &["a"], true);
|
||||
test(" a", 2, None, &[" a"], true);
|
||||
test(" \na", 2, None, &["a"], false);
|
||||
test(" a", 2, None, &["a"], false);
|
||||
test(" a", 2, None, &[" a"], false);
|
||||
test(" \na", 2, None, &["a"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -203,8 +207,11 @@ mod tests {
|
||||
test(" hi", vec![" hi"]);
|
||||
test("\nhi", vec!["hi"]);
|
||||
test(" \n hi", vec![" hi"]);
|
||||
test("hi ", vec!["hi"]);
|
||||
test("hi ", vec!["hi "]);
|
||||
test("hi` ", vec!["hi`"]);
|
||||
test("hi` ", vec!["hi` "]);
|
||||
test("hi` ", vec!["hi` "]);
|
||||
test("hi ", vec!["hi "]);
|
||||
test("hi ", vec!["hi "]);
|
||||
test("hi\n", vec!["hi"]);
|
||||
test("hi \n ", vec!["hi "]);
|
||||
test(" \n hi \n ", vec![" hi "]);
|
||||
|
@ -254,22 +254,22 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
|
||||
fn math(&mut self) -> Token<'s> {
|
||||
let mut inline = true;
|
||||
let mut display = false;
|
||||
if self.s.eat_if('[') {
|
||||
inline = false;
|
||||
display = true;
|
||||
}
|
||||
|
||||
let start = self.s.index();
|
||||
|
||||
let mut escaped = false;
|
||||
let mut dollar = inline;
|
||||
let mut dollar = !display;
|
||||
|
||||
let terminated = loop {
|
||||
match self.s.eat() {
|
||||
Some('$') if !escaped && dollar => break true,
|
||||
Some(']') if !escaped => dollar = true,
|
||||
Some(c) => {
|
||||
dollar = inline;
|
||||
dollar = !display;
|
||||
escaped = c == '\\' && !escaped;
|
||||
}
|
||||
None => break false,
|
||||
@ -277,15 +277,15 @@ impl<'s> Tokens<'s> {
|
||||
};
|
||||
|
||||
let end = self.s.index()
|
||||
- match (terminated, inline) {
|
||||
- match (terminated, display) {
|
||||
(false, _) => 0,
|
||||
(true, true) => 1,
|
||||
(true, false) => 2,
|
||||
(true, false) => 1,
|
||||
(true, true) => 2,
|
||||
};
|
||||
|
||||
Token::Math(TokenMath {
|
||||
formula: self.s.get(start .. end),
|
||||
inline,
|
||||
display,
|
||||
terminated,
|
||||
})
|
||||
}
|
||||
@ -470,8 +470,8 @@ mod tests {
|
||||
Token::Raw(TokenRaw { text, backticks, terminated })
|
||||
}
|
||||
|
||||
const fn Math(formula: &str, inline: bool, terminated: bool) -> Token {
|
||||
Token::Math(TokenMath { formula, inline, terminated })
|
||||
const fn Math(formula: &str, display: bool, terminated: bool) -> Token {
|
||||
Token::Math(TokenMath { formula, display, terminated })
|
||||
}
|
||||
|
||||
const fn UnicodeEscape(sequence: &str, terminated: bool) -> Token {
|
||||
@ -527,7 +527,7 @@ mod tests {
|
||||
('/', None, "//", LineComment("")),
|
||||
('/', None, "/**/", BlockComment("")),
|
||||
('/', Some(Markup), "*", Star),
|
||||
('/', Some(Markup), "$ $", Math(" ", true, true)),
|
||||
('/', Some(Markup), "$ $", Math(" ", false, true)),
|
||||
('/', Some(Markup), r"\\", Text(r"\")),
|
||||
('/', Some(Markup), "#let", Let),
|
||||
('/', Some(Code), "#if", If),
|
||||
@ -752,21 +752,21 @@ mod tests {
|
||||
#[test]
|
||||
fn test_tokenize_math_formulas() {
|
||||
// Test basic formula.
|
||||
t!(Markup: "$$" => Math("", true, true));
|
||||
t!(Markup: "$x$" => Math("x", true, true));
|
||||
t!(Markup: r"$\\$" => Math(r"\\", true, true));
|
||||
t!(Markup: "$[x + y]$" => Math("x + y", false, true));
|
||||
t!(Markup: r"$[\\]$" => Math(r"\\", false, true));
|
||||
t!(Markup: "$$" => Math("", false, true));
|
||||
t!(Markup: "$x$" => Math("x", false, true));
|
||||
t!(Markup: r"$\\$" => Math(r"\\", false, true));
|
||||
t!(Markup: "$[x + y]$" => Math("x + y", true, true));
|
||||
t!(Markup: r"$[\\]$" => Math(r"\\", true, true));
|
||||
|
||||
// Test unterminated.
|
||||
t!(Markup[""]: "$x" => Math("x", true, false));
|
||||
t!(Markup[""]: "$[x" => Math("x", false, false));
|
||||
t!(Markup[""]: "$[x]\n$" => Math("x]\n$", false, false));
|
||||
t!(Markup[""]: "$x" => Math("x", false, false));
|
||||
t!(Markup[""]: "$[x" => Math("x", true, false));
|
||||
t!(Markup[""]: "$[x]\n$" => Math("x]\n$", true, false));
|
||||
|
||||
// Test escape sequences.
|
||||
t!(Markup: r"$\$x$" => Math(r"\$x", true, true));
|
||||
t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", false, true));
|
||||
t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", false, false));
|
||||
t!(Markup: r"$\$x$" => Math(r"\$x", false, true));
|
||||
t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", true, true));
|
||||
t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", true, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
use std::fmt::{Arguments, Result, Write};
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::geom::{Angle, Length, Linear, Relative};
|
||||
|
||||
/// Pretty print an item and return the resulting string.
|
||||
pub fn pretty<T>(item: &T) -> String
|
||||
where
|
||||
T: Pretty,
|
||||
T: Pretty + ?Sized,
|
||||
{
|
||||
let mut p = Printer::new();
|
||||
item.pretty(&mut p);
|
||||
@ -29,6 +32,11 @@ impl Printer {
|
||||
Self { buf: String::new() }
|
||||
}
|
||||
|
||||
/// Push a character into the buffer.
|
||||
pub fn push(&mut self, c: char) {
|
||||
self.buf.push(c);
|
||||
}
|
||||
|
||||
/// Push a string into the buffer.
|
||||
pub fn push_str(&mut self, string: &str) {
|
||||
self.buf.push_str(string);
|
||||
@ -67,3 +75,64 @@ impl Write for Printer {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for i64 {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str(itoa::Buffer::new().format(*self));
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for f64 {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str(ryu::Buffer::new().format(*self));
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for str {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push('"');
|
||||
for c in self.chars() {
|
||||
match c {
|
||||
'\\' => p.push_str(r"\\"),
|
||||
'"' => p.push_str(r#"\""#),
|
||||
'\n' => p.push_str(r"\n"),
|
||||
'\r' => p.push_str(r"\r"),
|
||||
'\t' => p.push_str(r"\t"),
|
||||
_ => p.push(c),
|
||||
}
|
||||
}
|
||||
p.push('"');
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_pretty_display {
|
||||
($($type:ty),* $(,)?) => {
|
||||
$(impl Pretty for $type {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
write!(p, "{}", self).unwrap();
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
impl_pretty_display! {
|
||||
bool,
|
||||
Length,
|
||||
Angle,
|
||||
Relative,
|
||||
Linear,
|
||||
RgbaColor,
|
||||
Color,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_str() {
|
||||
assert_eq!(pretty("\n"), r#""\n""#);
|
||||
assert_eq!(pretty("\\"), r#""\\""#);
|
||||
assert_eq!(pretty("\""), r#""\"""#);
|
||||
}
|
||||
}
|
||||
|
@ -62,24 +62,28 @@ impl Pretty for Expr {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Self::None => p.push_str("none"),
|
||||
Self::Ident(v) => p.push_str(&v),
|
||||
Self::Bool(v) => write!(p, "{}", v).unwrap(),
|
||||
Self::Int(v) => p.push_str(itoa::Buffer::new().format(*v)),
|
||||
Self::Float(v) => p.push_str(ryu::Buffer::new().format(*v)),
|
||||
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||
Self::Angle(v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
|
||||
Self::Color(v) => write!(p, "{}", v).unwrap(),
|
||||
// TODO: Debug escapes a bit more than we want (e.g. apostrophes).
|
||||
// We probably need to do the escaping ourselves.
|
||||
Self::Str(v) => write!(p, "{:?}", &v).unwrap(),
|
||||
Self::Ident(v) => v.pretty(p),
|
||||
Self::Bool(v) => v.pretty(p),
|
||||
Self::Int(v) => v.pretty(p),
|
||||
Self::Float(v) => v.pretty(p),
|
||||
Self::Length(v, u) => {
|
||||
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
|
||||
}
|
||||
Self::Angle(v, u) => {
|
||||
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
|
||||
}
|
||||
Self::Percent(v) => {
|
||||
write!(p, "{}%", ryu::Buffer::new().format(*v)).unwrap();
|
||||
}
|
||||
Self::Color(v) => v.pretty(p),
|
||||
Self::Str(v) => v.pretty(p),
|
||||
Self::Array(v) => v.pretty(p),
|
||||
Self::Dict(v) => v.pretty(p),
|
||||
Self::Template(v) => pretty_template(v, p),
|
||||
Self::Group(v) => {
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
v.v.pretty(p);
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
Self::Block(v) => v.pretty(p),
|
||||
Self::Unary(v) => v.pretty(p),
|
||||
@ -98,12 +102,12 @@ pub type ExprArray = SpanVec<Expr>;
|
||||
|
||||
impl Pretty for ExprArray {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
p.join(self, ", ", |item, p| item.v.pretty(p));
|
||||
if self.len() == 1 {
|
||||
p.push_str(",");
|
||||
p.push(',');
|
||||
}
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,13 +116,13 @@ pub type ExprDict = Vec<Named>;
|
||||
|
||||
impl Pretty for ExprDict {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
if self.is_empty() {
|
||||
p.push_str(":");
|
||||
p.push(':');
|
||||
} else {
|
||||
p.join(self, ", ", |named, p| named.pretty(p));
|
||||
}
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +137,7 @@ pub struct Named {
|
||||
|
||||
impl Pretty for Named {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str(&self.name.v);
|
||||
self.name.v.pretty(p);
|
||||
p.push_str(": ");
|
||||
self.expr.v.pretty(p);
|
||||
}
|
||||
@ -147,9 +151,9 @@ pub fn pretty_template(template: &ExprTemplate, p: &mut Printer) {
|
||||
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() {
|
||||
pretty_func_template(call, p, false)
|
||||
} else {
|
||||
p.push_str("[");
|
||||
p.push('[');
|
||||
template.pretty(p);
|
||||
p.push_str("]");
|
||||
p.push(']');
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,15 +171,15 @@ pub struct ExprBlock {
|
||||
|
||||
impl Pretty for ExprBlock {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("{");
|
||||
p.push('{');
|
||||
if self.exprs.len() > 1 {
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
}
|
||||
p.join(&self.exprs, "; ", |expr, p| expr.v.pretty(p));
|
||||
if self.exprs.len() > 1 {
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
}
|
||||
p.push_str("}");
|
||||
p.push('}');
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +196,7 @@ impl Pretty for ExprUnary {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
self.op.v.pretty(p);
|
||||
if self.op.v == UnOp::Not {
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
}
|
||||
self.expr.v.pretty(p);
|
||||
}
|
||||
@ -258,9 +262,9 @@ pub struct ExprBinary {
|
||||
impl Pretty for ExprBinary {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
self.lhs.v.pretty(p);
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
self.op.v.pretty(p);
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
self.rhs.v.pretty(p);
|
||||
}
|
||||
}
|
||||
@ -419,9 +423,9 @@ pub struct ExprCall {
|
||||
impl Pretty for ExprCall {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
self.callee.v.pretty(p);
|
||||
p.push_str("(");
|
||||
p.push('(');
|
||||
self.args.v.pretty(p);
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,7 +448,7 @@ pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) {
|
||||
{
|
||||
// Previous arguments.
|
||||
if !head.is_empty() {
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
p.join(head, ", ", |item, p| item.pretty(p));
|
||||
}
|
||||
|
||||
@ -458,12 +462,12 @@ pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) {
|
||||
template.pretty(p);
|
||||
}
|
||||
} else if !call.args.v.is_empty() {
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
call.args.v.pretty(p);
|
||||
}
|
||||
|
||||
// Either end of header or end of body.
|
||||
p.push_str("]");
|
||||
p.push(']');
|
||||
}
|
||||
|
||||
/// The arguments to a function: `12, draw: false`.
|
||||
@ -508,7 +512,7 @@ pub struct ExprLet {
|
||||
impl Pretty for ExprLet {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("#let ");
|
||||
p.push_str(&self.pat.v);
|
||||
self.pat.v.pretty(p);
|
||||
if let Some(init) = &self.init {
|
||||
p.push_str(" = ");
|
||||
init.v.pretty(p);
|
||||
@ -531,7 +535,7 @@ impl Pretty for ExprIf {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("#if ");
|
||||
self.condition.v.pretty(p);
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
self.if_body.v.pretty(p);
|
||||
if let Some(expr) = &self.else_body {
|
||||
p.push_str(" #else ");
|
||||
@ -557,7 +561,7 @@ impl Pretty for ExprFor {
|
||||
self.pat.v.pretty(p);
|
||||
p.push_str(" #in ");
|
||||
self.iter.v.pretty(p);
|
||||
p.push_str(" ");
|
||||
p.push(' ');
|
||||
self.body.v.pretty(p);
|
||||
}
|
||||
}
|
||||
@ -574,11 +578,11 @@ pub enum ForPattern {
|
||||
impl Pretty for ForPattern {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Self::Value(v) => p.push_str(&v),
|
||||
Self::Value(v) => v.pretty(p),
|
||||
Self::KeyValue(k, v) => {
|
||||
p.push_str(&k);
|
||||
k.pretty(p);
|
||||
p.push_str(", ");
|
||||
p.push_str(&v);
|
||||
v.pretty(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ use std::ops::Deref;
|
||||
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
use crate::pretty::{Pretty, Printer};
|
||||
|
||||
/// An Unicode identifier with a few extra permissible characters.
|
||||
///
|
||||
/// In addition to what is specified in the [Unicode Standard][uax31], we allow:
|
||||
@ -28,6 +30,12 @@ impl Ident {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for Ident {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Ident {
|
||||
fn as_ref(&self) -> &str {
|
||||
self
|
||||
|
@ -64,9 +64,18 @@ mod tests {
|
||||
|
||||
// Raw.
|
||||
roundtrip("``");
|
||||
roundtrip("`lang 1`");
|
||||
test("``` hi```", "`hi`");
|
||||
test("``` ` ```", "```");
|
||||
roundtrip("`nolang 1`");
|
||||
roundtrip("```lang 1```");
|
||||
roundtrip("```lang 1 ```");
|
||||
roundtrip("```hi line ```");
|
||||
roundtrip("```py\ndef\n```");
|
||||
roundtrip("```\n line \n```");
|
||||
roundtrip("```\n`\n```");
|
||||
roundtrip("``` ` ```");
|
||||
test("```1 ```", "``");
|
||||
test("``` 1```", "`1`");
|
||||
test("``` 1 ```", "`1 `");
|
||||
test("```` ` ````", "``` ` ```");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -77,12 +86,12 @@ mod tests {
|
||||
roundtrip("{true}");
|
||||
roundtrip("{10}");
|
||||
roundtrip("{3.14}");
|
||||
roundtrip("{10pt}");
|
||||
roundtrip("{10.0pt}");
|
||||
roundtrip("{14.1deg}");
|
||||
roundtrip("{20%}");
|
||||
roundtrip("{20.0%}");
|
||||
roundtrip("{#abcdef}");
|
||||
roundtrip(r#"{"hi"}"#);
|
||||
test(r#"{"let's go"}"#, r#"{"let\'s go"}"#);
|
||||
test(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
|
||||
|
||||
// Arrays.
|
||||
roundtrip("{()}");
|
||||
|
@ -26,9 +26,9 @@ pub enum Node {
|
||||
impl Pretty for Node {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Self::Strong => p.push_str("*"),
|
||||
Self::Emph => p.push_str("_"),
|
||||
Self::Space => p.push_str(" "),
|
||||
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),
|
||||
@ -46,10 +46,10 @@ impl Pretty for Node {
|
||||
}
|
||||
}
|
||||
|
||||
/// A section heading: `# Introduction`.
|
||||
/// A section heading: `= Introduction`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NodeHeading {
|
||||
/// The section depth (numer of hashtags minus 1, capped at 5).
|
||||
/// The section depth (numer of equals signs minus 1, capped at 5).
|
||||
pub level: Spanned<u8>,
|
||||
/// The contents of the heading.
|
||||
pub contents: Tree,
|
||||
@ -58,7 +58,7 @@ pub struct NodeHeading {
|
||||
impl Pretty for NodeHeading {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
for _ in 0 ..= self.level.v {
|
||||
p.push_str("=");
|
||||
p.push('=');
|
||||
}
|
||||
self.contents.pretty(p);
|
||||
}
|
||||
@ -67,8 +67,7 @@ impl Pretty for NodeHeading {
|
||||
/// A raw block with optional syntax highlighting: `` `raw` ``.
|
||||
///
|
||||
/// Raw blocks start with 1 or 3+ 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.
|
||||
/// backticks.
|
||||
///
|
||||
/// When using at least three backticks, an optional language tag may follow
|
||||
/// directly after the backticks. This tag defines which language to
|
||||
@ -86,7 +85,7 @@ impl Pretty for NodeHeading {
|
||||
/// ````typst
|
||||
/// ```rust println!("hello!")```;
|
||||
/// ````
|
||||
/// - Blocks can span multiple lines.
|
||||
/// - Blocks can span multiple lines.
|
||||
/// ````typst
|
||||
/// ```rust
|
||||
/// loop {
|
||||
@ -94,34 +93,40 @@ impl Pretty for NodeHeading {
|
||||
/// }
|
||||
/// ```
|
||||
/// ````
|
||||
/// - 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.
|
||||
/// - Start with a space to omit the language tag (the space will be trimmed
|
||||
/// from the output).
|
||||
/// `````typst
|
||||
/// ```` This contains ```backticks``` and has no leading & trailing spaces. ````
|
||||
/// ```` 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, 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.
|
||||
/// - The first word of text wrapped in more than three backticks would always
|
||||
/// be interpreted as a language tag which means that text without leading
|
||||
/// space would be impossible.
|
||||
/// - 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.
|
||||
/// # 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 trim blocks with 3+ backticks as follows:
|
||||
/// - A single space or a sequence of whitespace followed by a newline at the start.
|
||||
/// - A single space or a newline followed by a sequence of whitespace at the end.
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
/// 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.
|
||||
@ -129,28 +134,65 @@ pub struct NodeRaw {
|
||||
/// 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,
|
||||
/// 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) {
|
||||
p.push_str("`");
|
||||
if let Some(lang) = &self.lang {
|
||||
p.push_str(&lang);
|
||||
p.push_str(" ");
|
||||
// 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;
|
||||
}
|
||||
// TODO: Technically, we should handle backticks in the lines by
|
||||
// wrapping with more backticks, and we should add space before the
|
||||
// first and/or after the last line if necessary.
|
||||
|
||||
// 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));
|
||||
p.push_str("`");
|
||||
|
||||
// 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('`');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,9 +170,9 @@ pub struct TokenRaw<'s> {
|
||||
pub struct TokenMath<'s> {
|
||||
/// The formula between the dollars.
|
||||
pub formula: &'s str,
|
||||
/// Whether the formula was surrounded by one dollar (true) or two dollars
|
||||
/// (false).
|
||||
pub inline: bool,
|
||||
/// Whether the formula is display-level, that is, it is surrounded by
|
||||
/// `$[..]`.
|
||||
pub display: bool,
|
||||
/// Whether the closing dollars were present.
|
||||
pub terminated: bool,
|
||||
}
|
||||
@ -243,8 +243,8 @@ impl<'s> Token<'s> {
|
||||
Self::Bool(_) => "boolean",
|
||||
Self::Int(_) => "integer",
|
||||
Self::Float(_) => "float",
|
||||
Self::Length(..) => "length",
|
||||
Self::Angle(..) => "angle",
|
||||
Self::Length(_, _) => "length",
|
||||
Self::Angle(_, _) => "angle",
|
||||
Self::Percent(_) => "percentage",
|
||||
Self::Color(_) => "color",
|
||||
Self::Str(_) => "string",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 24 KiB |
@ -9,6 +9,21 @@
|
||||
`#let x = 1` \
|
||||
`#[f 1]`
|
||||
|
||||
---
|
||||
// Multiline block splits paragraphs.
|
||||
|
||||
First
|
||||
```
|
||||
Second
|
||||
```
|
||||
Third
|
||||
|
||||
---
|
||||
// Lots of backticks inside.
|
||||
````
|
||||
```backticks```
|
||||
````
|
||||
|
||||
---
|
||||
// Trimming.
|
||||
|
||||
@ -18,7 +33,9 @@ The keyword ```rust let```.
|
||||
// Trimming depends on number backticks.
|
||||
<``> \
|
||||
<` untrimmed `> \
|
||||
<``` trimmed ```>
|
||||
<``` trimmed` ```> \
|
||||
<``` trimmed ```> \
|
||||
<``` trimmed```>
|
||||
|
||||
// Multiline trimming.
|
||||
```py
|
||||
@ -28,12 +45,6 @@ def hi():
|
||||
print("Hi!")
|
||||
```
|
||||
|
||||
---
|
||||
// Lots of backticks inside.
|
||||
````
|
||||
```backticks```
|
||||
````
|
||||
|
||||
---
|
||||
// Unterminated.
|
||||
// Error: 2:1-2:1 expected backtick(s)
|
||||
|
@ -335,7 +335,7 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) {
|
||||
}
|
||||
value.pretty(p);
|
||||
});
|
||||
p.push_str(")");
|
||||
p.push(')');
|
||||
|
||||
Value::Str(p.finish())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user