summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-28 15:35:56 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-28 15:36:32 +0100
commit4809e685a231a3ade2c78b75685ee859196c38c1 (patch)
treee3141236cca536c31c6ef4a6df6d218c16ba5a94
parent28c554ec2185a15e22f0408ce485ed4afe035e03 (diff)
More capable math calls
-rw-r--r--library/src/lib.rs7
-rw-r--r--library/src/math/accent.rs16
-rw-r--r--library/src/math/atom.rs58
-rw-r--r--library/src/math/attach.rs54
-rw-r--r--library/src/math/ctx.rs40
-rw-r--r--library/src/math/lr.rs82
-rw-r--r--library/src/math/mod.rs7
-rw-r--r--library/src/math/root.rs2
-rw-r--r--library/src/prelude.rs2
-rw-r--r--src/diag.rs1
-rw-r--r--src/ide/analyze.rs4
-rw-r--r--src/ide/complete.rs12
-rw-r--r--src/ide/highlight.rs25
-rw-r--r--src/model/content.rs10
-rw-r--r--src/model/dict.rs21
-rw-r--r--src/model/eval.rs40
-rw-r--r--src/model/func.rs10
-rw-r--r--src/model/library.rs3
-rw-r--r--src/model/methods.rs76
-rw-r--r--src/model/value.rs14
-rw-r--r--src/syntax/ast.rs63
-rw-r--r--src/syntax/kind.rs3
-rw-r--r--src/syntax/lexer.rs19
-rw-r--r--src/syntax/node.rs8
-rw-r--r--src/syntax/parser.rs56
-rw-r--r--src/syntax/reparser.rs7
-rw-r--r--tests/ref/math/simple.pngbin8334 -> 8333 bytes
-rw-r--r--tests/typ/compiler/dict.typ2
-rw-r--r--tests/typ/compiler/for.typ2
29 files changed, 376 insertions, 268 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 17f3de6d..41c621cb 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -191,12 +191,11 @@ fn items() -> LangItems {
layout::ListItem::Term(basics::TermItem { term, description }).pack()
},
formula: |body, block| math::FormulaNode { body, block }.pack(),
- math_atom: |atom| math::AtomNode(atom).pack(),
math_align_point: || math::AlignPointNode.pack(),
- math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
- math_attach: |base, sub, sup| {
- math::AttachNode { base, top: sub, bottom: sup }.pack()
+ math_delimited: |open, body, close| {
+ math::LrNode { body: open + body + close, size: None }.pack()
},
+ math_attach: |base, bottom, top| math::AttachNode { base, bottom, top }.pack(),
math_accent: |base, accent| math::AccentNode { base, accent }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
}
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
index 90e64b96..fdb59c5c 100644
--- a/library/src/math/accent.rs
+++ b/library/src/math/accent.rs
@@ -10,8 +10,9 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5);
///
/// ## Example
/// ```
+/// $grave(a) = accent(a, `)$ \
/// $arrow(a) = accent(a, arrow)$ \
-/// $grave(a) = accent(a, `)$
+/// $tilde(a) = accent(a, \u{0303})$
/// ```
///
/// ## Parameters
@@ -58,11 +59,22 @@ pub struct AccentNode {
impl AccentNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
- let accent = args.expect("accent")?;
+ let accent = args.expect::<Accent>("accent")?.0;
Ok(Self { base, accent }.pack())
}
}
+struct Accent(char);
+
+castable! {
+ Accent,
+ v: char => Self(v),
+ v: Content => match v.to::<TextNode>() {
+ Some(text) => Self(Value::Str(text.0.clone().into()).cast()?),
+ None => Err("expected text")?,
+ },
+}
+
impl LayoutMath for AccentNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_cramped(true));
diff --git a/library/src/math/atom.rs b/library/src/math/atom.rs
deleted file mode 100644
index 6d7359bf..00000000
--- a/library/src/math/atom.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use super::*;
-
-/// # Atom
-/// An atom in a math formula: `x`, `+`, `12`.
-///
-/// ## Parameters
-/// - text: EcoString (positional, required)
-/// The atom's text.
-///
-/// ## Category
-/// math
-#[func]
-#[capable(LayoutMath)]
-#[derive(Debug, Hash)]
-pub struct AtomNode(pub EcoString);
-
-#[node]
-impl AtomNode {
- fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.expect("text")?).pack())
- }
-}
-
-impl LayoutMath for AtomNode {
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut chars = self.0.chars();
- if let Some(glyph) = chars
- .next()
- .filter(|_| chars.next().is_none())
- .and_then(|c| GlyphFragment::try_new(ctx, c))
- {
- // A single letter that is available in the math font.
- if ctx.style.size == MathSize::Display
- && glyph.class == Some(MathClass::Large)
- {
- let height = scaled!(ctx, display_operator_min_height);
- ctx.push(glyph.stretch_vertical(ctx, height, Abs::zero()));
- } else {
- ctx.push(glyph);
- }
- } else if self.0.chars().all(|c| c.is_ascii_digit()) {
- // A number that should respect math styling and can therefore
- // not fall back to the normal text layout.
- let mut vec = vec![];
- for c in self.0.chars() {
- vec.push(GlyphFragment::new(ctx, c).into());
- }
- let frame = MathRow(vec).to_frame(ctx);
- ctx.push(frame);
- } else {
- // Anything else is handled by Typst's standard text layout.
- let frame = ctx.layout_non_math(&TextNode(self.0.clone()).pack())?;
- ctx.push(FrameFragment::new(frame).with_class(MathClass::Alphabetic));
- }
-
- Ok(())
- }
-}
diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs
index 2205e556..0d774839 100644
--- a/library/src/math/attach.rs
+++ b/library/src/math/attach.rs
@@ -51,17 +51,17 @@ impl LayoutMath for AttachNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let base = ctx.layout_fragment(&self.base)?;
- let mut sub = Frame::new(Size::zero());
+ let mut top = Frame::new(Size::zero());
if let Some(node) = &self.top {
ctx.style(ctx.style.for_subscript());
- sub = ctx.layout_frame(node)?;
+ top = ctx.layout_frame(node)?;
ctx.unstyle();
}
- let mut sup = Frame::new(Size::zero());
+ let mut bottom = Frame::new(Size::zero());
if let Some(node) = &self.bottom {
ctx.style(ctx.style.for_superscript());
- sup = ctx.layout_frame(node)?;
+ bottom = ctx.layout_frame(node)?;
ctx.unstyle();
}
@@ -76,9 +76,9 @@ impl LayoutMath for AttachNode {
});
if render_limits {
- limits(ctx, base, sub, sup)
+ limits(ctx, base, top, bottom)
} else {
- scripts(ctx, base, sub, sup, self.top.is_some() && self.bottom.is_some())
+ scripts(ctx, base, top, bottom, self.top.is_some() && self.bottom.is_some())
}
}
}
@@ -151,8 +151,8 @@ impl LayoutMath for LimitsNode {
fn scripts(
ctx: &mut MathContext,
base: MathFragment,
- sub: Frame,
sup: Frame,
+ sub: Frame,
both: bool,
) -> SourceResult<()> {
let sup_shift_up = if ctx.style.cramped {
@@ -191,21 +191,28 @@ fn scripts(
}
}
- let delta = base.italics_correction();
+ let italics = base.italics_correction();
+ let top_delta = match base.class() {
+ Some(MathClass::Large) => Abs::zero(),
+ _ => italics,
+ };
+ let bottom_delta = -italics;
let ascent = shift_up + sup.ascent();
let descent = shift_down + sub.descent();
let height = ascent + descent;
- let width = base.width() + sup.width().max(sub.width() - delta) + space_after;
+ let width = base.width()
+ + (sup.width() + top_delta).max(sub.width() + bottom_delta)
+ + space_after;
let base_pos = Point::with_y(ascent - base.ascent());
- let sup_pos = Point::with_x(base.width());
- let sub_pos = Point::new(base.width() - delta, height - sub.height());
+ let sup_pos = Point::with_x(base.width() + top_delta);
+ let sub_pos = Point::new(base.width() + bottom_delta, height - sub.height());
let class = base.class().unwrap_or(MathClass::Normal);
let mut frame = Frame::new(Size::new(width, height));
frame.set_baseline(ascent);
frame.push_frame(base_pos, base.to_frame(ctx));
- frame.push_frame(sub_pos, sub);
frame.push_frame(sup_pos, sup);
+ frame.push_frame(sub_pos, sub);
ctx.push(FrameFragment::new(frame).with_class(class));
Ok(())
@@ -215,30 +222,31 @@ fn scripts(
fn limits(
ctx: &mut MathContext,
base: MathFragment,
- sub: Frame,
- sup: Frame,
+ top: Frame,
+ bottom: Frame,
) -> SourceResult<()> {
let upper_gap_min = scaled!(ctx, upper_limit_gap_min);
let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min);
let lower_gap_min = scaled!(ctx, lower_limit_gap_min);
let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min);
- let sup_gap = upper_gap_min.max(upper_rise_min - sup.descent());
- let sub_gap = lower_gap_min.max(lower_drop_min - sub.ascent());
+ let top_gap = upper_gap_min.max(upper_rise_min - top.descent());
+ let bottom_gap = lower_gap_min.max(lower_drop_min - bottom.ascent());
let delta = base.italics_correction() / 2.0;
- let width = base.width().max(sup.width()).max(sub.width());
- let height = sup.height() + sup_gap + base.height() + sub_gap + sub.height();
- let base_pos = Point::new((width - base.width()) / 2.0, sup.height() + sup_gap);
- let sup_pos = Point::with_x((width - sup.width()) / 2.0 + delta);
- let sub_pos = Point::new((width - sub.width()) / 2.0 - delta, height - sub.height());
+ let width = base.width().max(top.width()).max(bottom.width());
+ let height = top.height() + top_gap + base.height() + bottom_gap + bottom.height();
+ let base_pos = Point::new((width - base.width()) / 2.0, top.height() + top_gap);
+ let sup_pos = Point::with_x((width - top.width()) / 2.0 + delta);
+ let sub_pos =
+ Point::new((width - bottom.width()) / 2.0 - delta, height - bottom.height());
let class = base.class().unwrap_or(MathClass::Normal);
let mut frame = Frame::new(Size::new(width, height));
frame.set_baseline(base_pos.y + base.ascent());
frame.push_frame(base_pos, base.to_frame(ctx));
- frame.push_frame(sub_pos, sub);
- frame.push_frame(sup_pos, sup);
+ frame.push_frame(sub_pos, bottom);
+ frame.push_frame(sup_pos, top);
ctx.push(FrameFragment::new(frame).with_class(class));
Ok(())
diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs
index 551d6cd7..547d3cc8 100644
--- a/library/src/math/ctx.rs
+++ b/library/src/math/ctx.rs
@@ -1,4 +1,5 @@
use ttf_parser::math::MathValue;
+use unicode_segmentation::UnicodeSegmentation;
use super::*;
@@ -129,6 +130,45 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Ok(self.layout_fragment(node)?.to_frame(self))
}
+ pub fn layout_text(&mut self, text: &EcoString) -> SourceResult<()> {
+ let mut chars = text.chars();
+ if let Some(glyph) = chars
+ .next()
+ .filter(|_| chars.next().is_none())
+ .and_then(|c| GlyphFragment::try_new(self, c))
+ {
+ // A single letter that is available in the math font.
+ if self.style.size == MathSize::Display
+ && glyph.class == Some(MathClass::Large)
+ {
+ let height = scaled!(self, display_operator_min_height);
+ self.push(glyph.stretch_vertical(self, height, Abs::zero()));
+ } else {
+ self.push(glyph);
+ }
+ } else if text.chars().all(|c| c.is_ascii_digit()) {
+ // A number that should respect math styling and can therefore
+ // not fall back to the normal text layout.
+ let mut vec = vec![];
+ for c in text.chars() {
+ vec.push(GlyphFragment::new(self, c).into());
+ }
+ let frame = MathRow(vec).to_frame(self);
+ self.push(frame);
+ } else {
+ // Anything else is handled by Typst's standard text layout.
+ let spaced = text.graphemes(true).count() > 1;
+ let frame = self.layout_non_math(&TextNode::packed(text.clone()))?;
+ self.push(
+ FrameFragment::new(frame)
+ .with_class(MathClass::Alphabetic)
+ .with_spaced(spaced),
+ );
+ }
+
+ Ok(())
+ }
+
pub fn size(&self) -> Abs {
self.scaled_size
}
diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs
index d5951050..a33810df 100644
--- a/library/src/math/lr.rs
+++ b/library/src/math/lr.rs
@@ -12,18 +12,29 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
/// ## Example
/// ```
/// $ lr(]a, b/2]) $
+/// $ lr(]sum_(x=1)^n] x, #size: 50%) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, variadic)
/// The delimited content, including the delimiters.
///
+/// - size: Rel<Length> (named)
+/// The size of the brackets, relative to the height of the wrapped content.
+///
+/// Defaults to `{100%}`.
+///
/// ## Category
/// math
#[func]
#[capable(LayoutMath)]
#[derive(Debug, Hash)]
-pub struct LrNode(pub Content);
+pub struct LrNode {
+ /// The delimited content, including the delimiters.
+ pub body: Content,
+ /// The size of the brackets.
+ pub size: Option<Rel<Length>>,
+}
#[node]
impl LrNode {
@@ -31,17 +42,18 @@ impl LrNode {
let mut body = Content::empty();
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
- body += AtomNode(','.into()).pack();
+ body += TextNode::packed(',');
}
body += arg;
}
- Ok(Self(body).pack())
+ let size = args.named("size")?;
+ Ok(Self { body, size }.pack())
}
}
impl LayoutMath for LrNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut row = ctx.layout_row(&self.0)?;
+ let mut row = ctx.layout_row(&self.body)?;
let axis = scaled!(ctx, axis_height);
let max_extent = row
@@ -51,22 +63,19 @@ impl LayoutMath for LrNode {
.max()
.unwrap_or_default();
- let height = 2.0 * max_extent;
- if let [first, .., last] = row.0.as_mut_slice() {
- for fragment in [first, last] {
- if !matches!(
- fragment.class(),
- Some(MathClass::Opening | MathClass::Closing | MathClass::Fence)
- ) {
- continue;
- }
-
- let MathFragment::Glyph(glyph) = *fragment else { continue };
- let short_fall = DELIM_SHORT_FALL.scaled(ctx);
- *fragment = MathFragment::Variant(
- glyph.stretch_vertical(ctx, height, short_fall),
- );
+ let height = self
+ .size
+ .unwrap_or(Rel::one())
+ .resolve(ctx.outer.chain(&ctx.map))
+ .relative_to(2.0 * max_extent);
+
+ match row.0.as_mut_slice() {
+ [one] => scale(ctx, one, height, None),
+ [first, .., last] => {
+ scale(ctx, first, height, Some(MathClass::Opening));
+ scale(ctx, last, height, Some(MathClass::Closing));
}
+ _ => {}
}
ctx.extend(row);
@@ -75,6 +84,28 @@ impl LayoutMath for LrNode {
}
}
+/// Scale a math fragment to a height.
+fn scale(
+ ctx: &mut MathContext,
+ fragment: &mut MathFragment,
+ height: Abs,
+ apply: Option<MathClass>,
+) {
+ if matches!(
+ fragment.class(),
+ Some(MathClass::Opening | MathClass::Closing | MathClass::Fence)
+ ) {
+ let MathFragment::Glyph(glyph) = *fragment else { return };
+ let short_fall = DELIM_SHORT_FALL.scaled(ctx);
+ *fragment =
+ MathFragment::Variant(glyph.stretch_vertical(ctx, height, short_fall));
+
+ if let Some(class) = apply {
+ fragment.set_class(class);
+ }
+ }
+}
+
/// # Floor
/// Floor an expression.
///
@@ -153,11 +184,14 @@ pub fn norm(args: &mut Args) -> SourceResult<Value> {
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
Ok(Value::Content(
- LrNode(Content::sequence(vec![
- AtomNode(left.into()).pack(),
- args.expect::<Content>("body")?,
- AtomNode(right.into()).pack(),
- ]))
+ LrNode {
+ body: Content::sequence(vec![
+ TextNode::packed(left),
+ args.expect::<Content>("body")?,
+ TextNode::packed(right),
+ ]),
+ size: None,
+ }
.pack(),
))
}
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index 636affed..4c002cea 100644
--- a/library/src/math/mod.rs
+++ b/library/src/math/mod.rs
@@ -4,7 +4,6 @@
mod ctx;
mod accent;
mod align;
-mod atom;
mod attach;
mod braced;
mod frac;
@@ -21,7 +20,6 @@ mod symbols;
pub use self::accent::*;
pub use self::align::*;
-pub use self::atom::*;
pub use self::attach::*;
pub use self::braced::*;
pub use self::frac::*;
@@ -263,6 +261,11 @@ impl LayoutMath for Content {
return Ok(());
}
+ if let Some(node) = self.to::<TextNode>() {
+ ctx.layout_text(&node.0)?;
+ return Ok(());
+ }
+
if let Some(node) = self.to::<SequenceNode>() {
for child in &node.0 {
child.layout_math(ctx)?;
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
index bae3bbb6..79bcfe38 100644
--- a/library/src/math/root.rs
+++ b/library/src/math/root.rs
@@ -155,7 +155,7 @@ fn layout(
/// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
- let node = index?.to::<AtomNode>()?;
+ let node = index?.to::<TextNode>()?;
let c = match node.0.as_str() {
"3" => '∛',
"4" => '∜',
diff --git a/library/src/prelude.rs b/library/src/prelude.rs
index 36d49dbf..b8f6025a 100644
--- a/library/src/prelude.rs
+++ b/library/src/prelude.rs
@@ -18,7 +18,7 @@ pub use typst::model::{
array, capability, capable, castable, dict, format_str, func, node, Args, Array,
AutoValue, Cast, CastInfo, Content, Dict, Finalize, Fold, Func, Introspector, Label,
Node, NodeId, NoneValue, Prepare, Resolve, Selector, Show, StabilityProvider, Str,
- StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt,
+ StyleChain, StyleMap, StyleVec, Symbol, Unlabellable, Value, Vm, Vt,
};
#[doc(no_inline)]
pub use typst::syntax::{Span, Spanned};
diff --git a/src/diag.rs b/src/diag.rs
index 054a7b03..1cf2f85b 100644
--- a/src/diag.rs
+++ b/src/diag.rs
@@ -68,7 +68,6 @@ impl SourceError {
/// Create a new, bare error.
#[track_caller]
pub fn new(span: Span, message: impl Into<EcoString>) -> Self {
- assert!(!span.is_detached());
Self {
span,
pos: ErrorPos::Full,
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs
index a1ac5778..c170186f 100644
--- a/src/ide/analyze.rs
+++ b/src/ide/analyze.rs
@@ -7,7 +7,9 @@ use crate::World;
/// Try to determine a set of possible values for an expression.
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
match node.cast::<ast::Expr>() {
- Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_)) => {
+ Some(
+ ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::MethodCall(_),
+ ) => {
if let Some(parent) = node.parent() {
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
return analyze(world, parent);
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index 83d0ca9c..83202e30 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -229,7 +229,7 @@ fn complete_math(ctx: &mut CompletionContext) -> bool {
}
// Behind existing atom or identifier: "$a|$" or "$abc|$".
- if matches!(ctx.leaf.kind(), SyntaxKind::MathAtom | SyntaxKind::MathIdent) {
+ if matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathIdent) {
ctx.from = ctx.leaf.offset();
math_completions(ctx);
return true;
@@ -274,7 +274,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool {
// Behind an expression plus dot: "emoji.|".
if_chain! {
if ctx.leaf.kind() == SyntaxKind::Dot
- || (matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathAtom)
+ || (ctx.leaf.kind() == SyntaxKind::Text
&& ctx.leaf.text() == ".");
if ctx.leaf.range().end == ctx.cursor;
if let Some(prev) = ctx.leaf.prev_sibling();
@@ -326,11 +326,15 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
}
}
_ => {
- for &method in methods_on(value.type_name()) {
+ for &(method, args) in methods_on(value.type_name()) {
ctx.completions.push(Completion {
kind: CompletionKind::Func,
label: method.into(),
- apply: Some(format_eco!("{method}(${{}})")),
+ apply: Some(if args {
+ format_eco!("{method}(${{}})")
+ } else {
+ format_eco!("{method}()${{}}")
+ }),
detail: None,
})
}
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index 7f7ad6ee..d8f15f00 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -1,4 +1,4 @@
-use crate::syntax::{LinkedNode, SyntaxKind};
+use crate::syntax::{ast, LinkedNode, SyntaxKind};
/// Syntax highlighting categories.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -115,19 +115,17 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Formula => None,
SyntaxKind::Math => None,
- SyntaxKind::MathAtom => None,
SyntaxKind::MathIdent => highlight_ident(node),
SyntaxKind::MathDelimited => None,
SyntaxKind::MathAttach => None,
SyntaxKind::MathFrac => None,
SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
- SyntaxKind::Hashtag if node.before_error() => None,
SyntaxKind::Hashtag => node
- .next_leaf()
- .filter(|node| node.kind() != SyntaxKind::Dollar)
- .as_ref()
- .and_then(highlight),
+ .next_sibling()
+ .filter(|node| node.cast::<ast::Expr>().map_or(false, |e| e.hashtag()))
+ .and_then(|node| node.leftmost_leaf())
+ .and_then(|node| highlight(&node)),
SyntaxKind::LeftBrace => Some(Category::Punctuation),
SyntaxKind::RightBrace => Some(Category::Punctuation),
@@ -248,12 +246,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
/// Highlight an identifier based on context.
fn highlight_ident(node: &LinkedNode) -> Option<Category> {
match node.parent_kind() {
- Some(
- SyntaxKind::Markup
- | SyntaxKind::Math
- | SyntaxKind::MathFrac
- | SyntaxKind::MathAttach,
- ) => Some(Category::Interpolated),
Some(SyntaxKind::FuncCall) => Some(Category::Function),
Some(SyntaxKind::FieldAccess)
if node.parent().and_then(|p| p.parent_kind())
@@ -287,6 +279,13 @@ fn highlight_ident(node: &LinkedNode) -> Option<Category> {
{
Some(Category::Function)
}
+ Some(
+ SyntaxKind::Markup
+ | SyntaxKind::Math
+ | SyntaxKind::MathFrac
+ | SyntaxKind::MathAttach,
+ ) => Some(Category::Interpolated),
+ _ if node.kind() == SyntaxKind::MathIdent => Some(Category::Interpolated),
_ => None,
}
}
diff --git a/src/model/content.rs b/src/model/content.rs
index df910a58..143f97aa 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -183,12 +183,18 @@ impl Content {
}
/// Whether the contained node is of type `T`.
- pub fn is<T: 'static>(&self) -> bool {
+ pub fn is<T>(&self) -> bool
+ where
+ T: Capable + 'static,
+ {
(*self.obj).as_any().is::<T>()
}
/// Cast to `T` if the contained node is of type `T`.
- pub fn to<T: 'static>(&self) -> Option<&T> {
+ pub fn to<T>(&self) -> Option<&T>
+ where
+ T: Capable + 'static,
+ {
(*self.obj).as_any().downcast_ref::<T>()
}
diff --git a/src/model/dict.rs b/src/model/dict.rs
index 76d194a8..7165fbbe 100644
--- a/src/model/dict.rs
+++ b/src/model/dict.rs
@@ -3,8 +3,8 @@ use std::fmt::{self, Debug, Formatter, Write};
use std::ops::{Add, AddAssign};
use std::sync::Arc;
-use super::{Args, Array, Func, Str, Value, Vm};
-use crate::diag::{bail, SourceResult, StrResult};
+use super::{array, Array, Str, Value};
+use crate::diag::StrResult;
use crate::syntax::is_ident;
use crate::util::{format_eco, ArcExt, EcoString};
@@ -104,17 +104,12 @@ impl Dict {
self.0.values().cloned().collect()
}
- /// Transform each pair in the dictionary with a function.
- pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Array> {
- if func.argc().map_or(false, |count| count != 2) {
- bail!(func.span(), "function must have exactly two parameters");
- }
- self.iter()
- .map(|(key, value)| {
- let args =
- Args::new(func.span(), [Value::Str(key.clone()), value.clone()]);
- func.call(vm, args)
- })
+ /// Return the values of the dictionary as an array of pairs (arrays of
+ /// length two).
+ pub fn pairs(&self) -> Array {
+ self.0
+ .iter()
+ .map(|(k, v)| Value::Array(array![k.clone(), v.clone()]))
.collect()
}
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 44f08a76..b63069bf 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -344,7 +344,6 @@ impl Eval for ast::Expr {
Self::Term(v) => v.eval(vm).map(Value::Content),
Self::Formula(v) => v.eval(vm).map(Value::Content),
Self::Math(v) => v.eval(vm).map(Value::Content),
- Self::MathAtom(v) => v.eval(vm).map(Value::Content),
Self::MathIdent(v) => v.eval(vm),
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
@@ -552,21 +551,13 @@ impl Eval for ast::Math {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(Content::sequence(
self.exprs()
- .map(|expr| Ok(expr.eval(vm)?.display_in_math()))
+ .map(|expr| Ok(expr.eval(vm)?.display()))
.collect::<SourceResult<_>>()?,
)
.spanned(self.span()))
}
}
-impl Eval for ast::MathAtom {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math_atom)(self.get().clone()))
- }
-}
-
impl Eval for ast::MathIdent {
type Output = Value;
@@ -587,9 +578,9 @@ impl Eval for ast::MathDelimited {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let open = self.open().eval(vm)?.display_in_math();
+ let open = self.open().eval(vm)?.display();
let body = self.body().eval(vm)?;
- let close = self.close().eval(vm)?.display_in_math();
+ let close = self.close().eval(vm)?.display();
Ok((vm.items.math_delimited)(open, body, close))
}
}
@@ -598,16 +589,13 @@ impl Eval for ast::MathAttach {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let base = self.base().eval(vm)?.display_in_math();
- let sub = self
+ let base = self.base().eval(vm)?.display();
+ let bottom = self
.bottom()
- .map(|expr| expr.eval(vm).map(Value::display_in_math))
- .transpose()?;
- let sup = self
- .top()
- .map(|expr| expr.eval(vm).map(Value::display_in_math))
+ .map(|expr| expr.eval(vm).map(Value::display))
.transpose()?;
- Ok((vm.items.math_attach)(base, sub, sup))
+ let top = self.top().map(|expr| expr.eval(vm).map(Value::display)).transpose()?;
+ Ok((vm.items.math_attach)(base, bottom, top))
}
}
@@ -615,8 +603,8 @@ impl Eval for ast::MathFrac {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let num = self.num().eval(vm)?.display_in_math();
- let denom = self.denom().eval(vm)?.display_in_math();
+ let num = self.num().eval(vm)?.display();
+ let denom = self.denom().eval(vm)?.display();
Ok((vm.items.math_frac)(num, denom))
}
}
@@ -945,15 +933,15 @@ impl Eval for ast::FuncCall {
}
}
- let mut body = (vm.items.math_atom)('('.into());
+ let mut body = (vm.items.text)('('.into());
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
- body += (vm.items.math_atom)(','.into());
+ body += (vm.items.text)(','.into());
}
body += arg;
}
- body += (vm.items.math_atom)(')'.into());
- return Ok(Value::Content(callee.display_in_math() + body));
+ body += (vm.items.text)(')'.into());
+ return Ok(Value::Content(callee.display() + body));
}
let callee = callee.cast::<Func>().at(callee_span)?;
diff --git a/src/model/func.rs b/src/model/func.rs
index 8cf3ea99..1ccb0107 100644
--- a/src/model/func.rs
+++ b/src/model/func.rs
@@ -389,6 +389,7 @@ impl<'a> CapturesVisitor<'a> {
// actually bind a new name are handled below (individually through
// the expressions that contain them).
Some(ast::Expr::Ident(ident)) => self.capture(ident),
+ Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident),
// Code and content blocks create a scope.
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
@@ -483,6 +484,15 @@ impl<'a> CapturesVisitor<'a> {
}
}
}
+
+ /// Capture a variable in math mode if it isn't internal.
+ fn capture_in_math(&mut self, ident: ast::MathIdent) {
+ if self.internal.get(&ident).is_err() {
+ if let Ok(value) = self.external.get_in_math(&ident) {
+ self.captures.define_captured(ident.take(), value.clone());
+ }
+ }
+ }
}
#[cfg(test)]
diff --git a/src/model/library.rs b/src/model/library.rs
index 4208a4c7..c87ca095 100644
--- a/src/model/library.rs
+++ b/src/model/library.rs
@@ -67,8 +67,6 @@ pub struct LangItems {
pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`.
pub formula: fn(body: Content, block: bool) -> Content,
- /// An atom in a formula: `x`, `+`, `12`.
- pub math_atom: fn(atom: EcoString) -> Content,
/// An alignment point in a formula: `&`.
pub math_align_point: fn() -> Content,
/// Matched delimiters surrounding math in a formula: `[x + y]`.
@@ -110,7 +108,6 @@ impl Hash for LangItems {
self.enum_item.hash(state);
self.term_item.hash(state);
self.formula.hash(state);
- self.math_atom.hash(state);
self.math_align_point.hash(state);
self.math_delimited.hash(state);
self.math_attach.hash(state);
diff --git a/src/model/methods.rs b/src/model/methods.rs
index 1671a5c4..5da64fa2 100644
--- a/src/model/methods.rs
+++ b/src/model/methods.rs
@@ -107,7 +107,7 @@ pub fn call(
"at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?,
"keys" => Value::Array(dict.keys()),
"values" => Value::Array(dict.values()),
- "pairs" => Value::Array(dict.map(vm, args.expect("function")?)?),
+ "pairs" => Value::Array(dict.pairs()),
_ => return missing(),
},
@@ -211,35 +211,61 @@ fn missing_method(type_name: &str, method: &str) -> String {
format!("type {type_name} has no method `{method}`")
}
-/// List the available methods for a type.
-pub fn methods_on(type_name: &str) -> &[&'static str] {
+/// List the available methods for a type and whether they take arguments.
+pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
match type_name {
- "color" => &["lighten", "darken", "negate"],
+ "color" => &[("lighten", true), ("darken", true), ("negate", false)],
"string" => &[
- "len",
- "at",
- "contains",
- "ends-with",
- "find",
- "first",
- "last",
- "match",
- "matches",
- "position",
- "replace",
- "slice",
- "split",
- "starts-with",
- "trim",
+ ("len", false),
+ ("at", true),
+ ("contains", true),
+ ("ends-with", true),
+ ("find", true),
+ ("first", false),
+ ("last", false),
+ ("match", true),
+ ("matches", true),
+ ("position", true),
+ ("replace", true),
+ ("slice", true),
+ ("split", true),
+ ("starts-with", true),
+ ("trim", true),
],
"array" => &[
- "all", "any", "at", "contains", "filter", "find", "first", "flatten", "fold",
- "insert", "join", "last", "len", "map", "pop", "position", "push", "remove",
- "rev", "slice", "sorted",
+ ("all", true),
+ ("any", true),
+ ("at", true),
+ ("contains", true),
+ ("filter", true),
+ ("find", true),
+ ("first", false),
+ ("flatten", false),
+ ("fold", true),
+ ("insert", true),
+ ("join", true),
+ ("last", false),
+ ("len", false),
+ ("map", true),
+ ("pop", false),
+ ("position", true),
+ ("push", true),
+ ("remove", true),
+ ("rev", false),
+ ("slice", true),
+ ("sorted", false),
],
- "dictionary" => &["at", "insert", "keys", "len", "pairs", "remove", "values"],
- "function" => &["where", "with"],
- "arguments" => &["named", "pos"],
+ "dictionary" => &[
+ ("at", true),
+ ("insert", true),
+ ("keys", false),
+ ("len", false),
+ ("pairs", false),
+ ("remove", true),
+ ("values", false),
+ ],
+ "function" => &[("where", true), ("with", true)],
+ "arguments" => &[("named", false), ("pos", false)],
_ => &[],
}
}
diff --git a/src/model/value.rs b/src/model/value.rs
index 4b9fa5f7..ea17349e 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -145,16 +145,6 @@ impl Value {
}
}
- /// Return the display representation of the value in math mode.
- pub fn display_in_math(self) -> Content {
- match self {
- Self::Int(v) => item!(math_atom)(format_eco!("{}", v)),
- Self::Float(v) => item!(math_atom)(format_eco!("{}", v)),
- Self::Symbol(v) => item!(math_atom)(v.get().into()),
- _ => self.display(),
- }
- }
-
/// Try to extract documentation for the value.
pub fn docs(&self) -> Option<&'static str> {
match self {
@@ -447,8 +437,8 @@ primitive! { Label: "label", Label }
primitive! { Content: "content",
Content,
None => Content::empty(),
- Symbol(symbol) => item!(text)(symbol.get().into()),
- Str(text) => item!(text)(text.into())
+ Symbol(v) => item!(text)(v.get().into()),
+ Str(v) => item!(text)(v.into())
}
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 45f79685..5704f171 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -115,8 +115,6 @@ pub enum Expr {
Formula(Formula),
/// A math formula: `$x$`, `$ x^2 $`.
Math(Math),
- /// An atom in a math formula: `x`, `+`, `12`.
- MathAtom(MathAtom),
/// An identifier in a math formula: `pi`.
MathIdent(MathIdent),
/// An alignment point in a math formula: `&`.
@@ -219,7 +217,6 @@ impl AstNode for Expr {
SyntaxKind::TermItem => node.cast().map(Self::Term),
SyntaxKind::Formula => node.cast().map(Self::Formula),
SyntaxKind::Math => node.cast().map(Self::Math),
- SyntaxKind::MathAtom => node.cast().map(Self::MathAtom),
SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
@@ -280,7 +277,6 @@ impl AstNode for Expr {
Self::Term(v) => v.as_untyped(),
Self::Formula(v) => v.as_untyped(),
Self::Math(v) => v.as_untyped(),
- Self::MathAtom(v) => v.as_untyped(),
Self::MathIdent(v) => v.as_untyped(),
Self::MathAlignPoint(v) => v.as_untyped(),
Self::MathDelimited(v) => v.as_untyped(),
@@ -320,6 +316,42 @@ impl AstNode for Expr {
}
}
+impl Expr {
+ /// Can this expression be embedded into markup with a hashtag?
+ pub fn hashtag(&self) -> bool {
+ match self {
+ Self::Ident(_) => true,
+ Self::None(_) => true,
+ Self::Auto(_) => true,
+ Self::Bool(_) => true,
+ Self::Int(_) => true,
+ Self::Float(_) => true,
+ Self::Numeric(_) => true,
+ Self::Str(_) => true,
+ Self::Code(_) => true,
+ Self::Content(_) => true,
+ Self::Array(_) => true,
+ Self::Dict(_) => true,
+ Self::Parenthesized(_) => true,
+ Self::FieldAccess(_) => true,
+ Self::FuncCall(_) => true,
+ Self::MethodCall(_) => true,
+ Self::Let(_) => true,
+ Self::Set(_) => true,
+ Self::Show(_) => true,
+ Self::Conditional(_) => true,
+ Self::While(_) => true,
+ Self::For(_) => true,
+ Self::Import(_) => true,
+ Self::Include(_) => true,
+ Self::Break(_) => true,
+ Self::Continue(_) => true,
+ Self::Return(_) => true,
+ _ => false,
+ }
+ }
+}
+
impl Default for Expr {
fn default() -> Self {
Expr::Space(Space::default())
@@ -393,18 +425,23 @@ impl Shorthand {
"..." => '…',
"*" => '∗',
"!=" => '≠',
+ "<<" => '≪',
+ "<<<" => '⋘',
+ ">>" => '≫',
+ ">>>" => '⋙',
"<=" => '≤',
">=" => '≥',
"<-" => '←',
"->" => '→',
"=>" => '⇒',
+ "|->" => '↦',
+ "|=>" => '⤇',
+ "<->" => '↔',
+ "<=>" => '⇔',
":=" => '≔',
"[|" => '⟦',
"|]" => '⟧',
"||" => '‖',
- "|->" => '↦',
- "<->" => '↔',
- "<=>" => '⇔',
_ => char::default(),
}
}
@@ -661,18 +698,6 @@ impl Math {
}
node! {
- /// A atom in a formula: `x`, `+`, `12`.
- MathAtom
-}
-
-impl MathAtom {
- /// Get the atom's text.
- pub fn get(&self) -> &EcoString {
- self.0.text()
- }
-}
-
-node! {
/// An identifier in a math formula: `pi`.
MathIdent
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index aa4a5cfe..b2b65a62 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -59,8 +59,6 @@ pub enum SyntaxKind {
/// Mathematical markup.
Math,
- /// An atom in math: `x`, `+`, `12`.
- MathAtom,
/// An identifier in math: `pi`.
MathIdent,
/// An alignment point in math: `&`.
@@ -345,7 +343,6 @@ impl SyntaxKind {
Self::Formula => "math formula",
Self::Math => "math",
Self::MathIdent => "math identifier",
- Self::MathAtom => "math atom",
Self::MathAlignPoint => "math alignment point",
Self::MathDelimited => "delimited math",
Self::MathAttach => "math attachments",
diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs
index d4548b8b..d267a05b 100644
--- a/src/syntax/lexer.rs
+++ b/src/syntax/lexer.rs
@@ -380,21 +380,28 @@ impl Lexer<'_> {
'\\' => self.backslash(),
'"' => self.string(),
+ '*' => SyntaxKind::Shorthand,
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand,
- '<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
- '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
+ '|' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
'!' if self.s.eat_if('=') => SyntaxKind::Shorthand,
+ '<' if self.s.eat_if("<<") => SyntaxKind::Shorthand,
+ '<' if self.s.eat_if('<') => SyntaxKind::Shorthand,
+ '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand,
+ '>' if self.s.eat_if('>') => SyntaxKind::Shorthand,
+
+ '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
+ '<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'>' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('-') => SyntaxKind::Shorthand,
'-' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'=' if self.s.eat_if('>') => SyntaxKind::Shorthand,
+
':' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'[' if self.s.eat_if('|') => SyntaxKind::Shorthand,
'|' if self.s.eat_if(']') => SyntaxKind::Shorthand,
'|' if self.s.eat_if('|') => SyntaxKind::Shorthand,
- '*' => SyntaxKind::Shorthand,
'#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag,
'_' => SyntaxKind::Underscore,
@@ -410,11 +417,11 @@ impl Lexer<'_> {
}
// Other math atoms.
- _ => self.atom(start, c),
+ _ => self.math_text(start, c),
}
}
- fn atom(&mut self, start: usize, c: char) -> SyntaxKind {
+ fn math_text(&mut self, start: usize, c: char) -> SyntaxKind {
// Keep numbers and grapheme clusters together.
if c.is_numeric() {
self.s.eat_while(char::is_numeric);
@@ -427,7 +434,7 @@ impl Lexer<'_> {
.map_or(0, str::len);
self.s.jump(start + len);
}
- SyntaxKind::MathAtom
+ SyntaxKind::Text
}
}
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index a0fa5e1e..ed000788 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -713,14 +713,6 @@ impl<'a> LinkedNode<'a> {
Some(next)
}
}
-
- /// Whether an error follows directly after the node.
- pub fn before_error(&self) -> bool {
- let Some(parent) = self.parent() else { return false };
- let Some(index) = self.index.checked_add(1) else { return false };
- let Some(node) = parent.node.children().nth(index) else { return false };
- node.kind().is_error()
- }
}
/// Access to leafs.
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 07730533..1b5c10a3 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -234,24 +234,24 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
SyntaxKind::Hashtag => embedded_code_expr(p),
SyntaxKind::MathIdent => {
p.eat();
- while p.directly_at(SyntaxKind::MathAtom)
+ while p.directly_at(SyntaxKind::Text)
&& p.current_text() == "."
&& matches!(
p.lexer.clone().next(),
- SyntaxKind::MathIdent | SyntaxKind::MathAtom
+ SyntaxKind::MathIdent | SyntaxKind::Text
)
{
p.convert(SyntaxKind::Dot);
p.convert(SyntaxKind::Ident);
p.wrap(m, SyntaxKind::FieldAccess);
}
- if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
+ if p.directly_at(SyntaxKind::Text) && p.current_text() == "(" {
math_args(p);
p.wrap(m, SyntaxKind::FuncCall);
}
}
- SyntaxKind::MathAtom | SyntaxKind::Shorthand => {
+ SyntaxKind::Text | SyntaxKind::Shorthand => {
if math_class(p.current_text()) == Some(MathClass::Fence) {
math_delimited(p, MathClass::Fence)
} else if math_class(p.current_text()) == Some(MathClass::Opening) {
@@ -374,16 +374,32 @@ fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usiz
}
fn math_args(p: &mut Parser) {
- p.assert(SyntaxKind::MathAtom);
+ p.assert(SyntaxKind::Text);
+
let m = p.marker();
- let mut m2 = p.marker();
+ let mut arg = p.marker();
+ let mut namable = true;
+ let mut named = None;
+
while !p.eof() && !p.at(SyntaxKind::Dollar) {
+ if namable
+ && (p.at(SyntaxKind::MathIdent) || p.at(SyntaxKind::Text))
+ && p.text[p.current_end()..].starts_with(':')
+ {
+ p.convert(SyntaxKind::Ident);
+ p.convert(SyntaxKind::Colon);
+ named = Some(arg);
+ arg = p.marker();
+ }
+
match p.current_text() {
")" => break,
"," => {
- p.wrap(m2, SyntaxKind::Math);
+ maybe_wrap_in_math(p, arg, named);
p.convert(SyntaxKind::Comma);
- m2 = p.marker();
+ arg = p.marker();
+ namable = true;
+ named = None;
continue;
}
_ => {}
@@ -394,12 +410,30 @@ fn math_args(p: &mut Parser) {
if !p.progress(prev) {
p.unexpected();
}
+
+ namable = false;
}
- if m2 != p.marker() {
- p.wrap(m2, SyntaxKind::Math);
+
+ if arg != p.marker() {
+ maybe_wrap_in_math(p, arg, named);
}
+
p.wrap(m, SyntaxKind::Args);
- p.expect(SyntaxKind::MathAtom);
+ if !p.eat_if(SyntaxKind::Text) {
+ p.expected("closing paren");
+ p.balanced = false;
+ }
+}
+
+fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) {
+ let exprs = p.post_process(arg).filter(|node| node.is::<ast::Expr>()).count();
+ if exprs != 1 {
+ p.wrap(arg, SyntaxKind::Math);
+ }
+
+ if let Some(m) = named {
+ p.wrap(m, SyntaxKind::Named);
+ }
}
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs
index de845abf..a876e86b 100644
--- a/src/syntax/reparser.rs
+++ b/src/syntax/reparser.rs
@@ -99,9 +99,9 @@ fn try_reparse(
&& (parent_kind.is_none() || parent_kind == Some(SyntaxKind::ContentBlock))
&& !overlap.is_empty()
{
- // Add one node of slack in both directions.
+ // Add slack in both directions.
let children = node.children_mut();
- let mut start = overlap.start.saturating_sub(1);
+ let mut start = overlap.start.saturating_sub(2);
let mut end = (overlap.end + 1).min(children.len());
// Expand to the left.
@@ -242,7 +242,7 @@ mod tests {
#[test]
fn test_reparse_markup() {
- test("abc~def~ghi", 5..6, "+", true);
+ test("abc~def~gh~", 5..6, "+", true);
test("~~~~~~~", 3..4, "A", true);
test("abc~~", 1..2, "", true);
test("#var. hello", 5..6, " ", false);
@@ -264,7 +264,6 @@ mod tests {
test("#show f: a => b..", 16..16, "c", false);
test("#for", 4..4, "//", false);
test("a\n#let \nb", 7..7, "i", true);
- test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", true);
test(r"#{{let x = z}; a = 1} b", 7..7, "//", false);
test(r#"a ```typst hello```"#, 16..17, "", false);
}
diff --git a/tests/ref/math/simple.png b/tests/ref/math/simple.png
index 4daa52e1..a0a864af 100644
--- a/tests/ref/math/simple.png
+++ b/tests/ref/math/simple.png
Binary files differ
diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ
index c34b5478..02097724 100644
--- a/tests/typ/compiler/dict.typ
+++ b/tests/typ/compiler/dict.typ
@@ -49,7 +49,7 @@
#test("c" in dict, true)
#test(dict.len(), 3)
#test(dict.values(), (3, 1, 2))
-#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2")
+#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3b1c2")
#{ dict.remove("c") }
#test("c" in dict, false)
diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ
index cf717411..7833aad7 100644
--- a/tests/typ/compiler/for.typ
+++ b/tests/typ/compiler/for.typ
@@ -34,7 +34,7 @@
// Map captured arguments.
#let f1(..args) = args.pos().map(repr)
-#let f2(..args) = args.named().pairs((k, v) => repr(k) + ": " + repr(v))
+#let f2(..args) = args.named().pairs().map(p => repr(p.first()) + ": " + repr(p.last()))
#let f(..args) = (f1(..args) + f2(..args)).join(", ")
#f(1, a: 2)