diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index e00007f3c..0ce6bf8aa 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -260,6 +260,7 @@ fn highlight_ident(node: &LinkedNode) -> Option { if let Some(next) = &next_leaf { if node.range().end == next.offset() && matches!(next.kind(), SyntaxKind::LeftParen | SyntaxKind::LeftBracket) + && matches!(next.parent_kind(), Some(SyntaxKind::Args | SyntaxKind::Params)) { return Some(Category::Function); } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 5933d481b..9f951389d 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -230,9 +230,11 @@ fn math_expr(p: &mut Parser) { fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { let m = p.marker(); + let mut continuable = false; match p.current() { SyntaxKind::Hashtag => embedded_code_expr(p), SyntaxKind::MathIdent => { + continuable = true; p.eat(); while p.directly_at(SyntaxKind::Text) && p.current_text() == "." @@ -245,30 +247,41 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { p.convert(SyntaxKind::Ident); p.wrap(m, SyntaxKind::FieldAccess); } - if p.directly_at(SyntaxKind::Text) && p.current_text() == "(" { + if min_prec < 3 && p.directly_at(SyntaxKind::Text) && p.current_text() == "(" + { math_args(p); p.wrap(m, SyntaxKind::FuncCall); + continuable = false; } } 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) { - math_delimited(p, MathClass::Closing) - } else { - p.eat() + continuable = matches!( + math_class(p.current_text()), + None | Some(MathClass::Alphabetic) + ); + if !maybe_delimited(p, true) { + p.eat(); } } - SyntaxKind::Linebreak - | SyntaxKind::Escape - | SyntaxKind::MathAlignPoint - | SyntaxKind::Str => p.eat(), + SyntaxKind::Linebreak | SyntaxKind::MathAlignPoint => p.eat(), + SyntaxKind::Escape | SyntaxKind::Str => { + continuable = true; + p.eat(); + } _ => p.expected("expression"), } + if continuable + && min_prec < 3 + && p.prev_end() == p.current_start() + && maybe_delimited(p, false) + { + p.wrap(m, SyntaxKind::Math); + } + while !p.eof() && !p.at(stop) { let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { break; @@ -302,6 +315,18 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { } } +fn maybe_delimited(p: &mut Parser, allow_fence: bool) -> bool { + if allow_fence && math_class(p.current_text()) == Some(MathClass::Fence) { + math_delimited(p, MathClass::Fence); + true + } else if math_class(p.current_text()) == Some(MathClass::Opening) { + math_delimited(p, MathClass::Closing); + true + } else { + false + } +} + fn math_delimited(p: &mut Parser, stop: MathClass) { let m = p.marker(); p.eat(); @@ -361,10 +386,10 @@ fn math_class(text: &str) -> Option { fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { match kind { SyntaxKind::Underscore => { - Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 3)) } SyntaxKind::Hat => { - Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 3)) } SyntaxKind::Slash => { Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1)) diff --git a/tests/ref/compiler/highlight.png b/tests/ref/compiler/highlight.png index 317a2128f..dba5591e9 100644 Binary files a/tests/ref/compiler/highlight.png and b/tests/ref/compiler/highlight.png differ diff --git a/tests/ref/math/attach.png b/tests/ref/math/attach.png index 27843eb43..9c92a93d0 100644 Binary files a/tests/ref/math/attach.png and b/tests/ref/math/attach.png differ diff --git a/tests/ref/math/frac.png b/tests/ref/math/frac.png index a3a9a3ae1..d0ac9c1a5 100644 Binary files a/tests/ref/math/frac.png and b/tests/ref/math/frac.png differ diff --git a/tests/typ/compiler/highlight.typ b/tests/typ/compiler/highlight.typ index 6c6ec802e..2dc2474a5 100644 --- a/tests/typ/compiler/highlight.typ +++ b/tests/typ/compiler/highlight.typ @@ -5,7 +5,7 @@ #set hello() #set hello.world() #set hello.my.world() - +#let foo(x) = x * 2 #show heading: func #show module.func: func #show module.func: it => {} @@ -30,6 +30,7 @@ $ hello.my.world $ $ hello.my.world() $ $ hello.my().world $ $ hello.my().world() $ +$ f_zeta(x), f_zeta(x)/1 $ $ emph(hello) $ $ emph(hello()) $ diff --git a/tests/typ/math/attach.typ b/tests/typ/math/attach.typ index cf3e9521b..89ecfc48d 100644 --- a/tests/typ/math/attach.typ +++ b/tests/typ/math/attach.typ @@ -6,8 +6,9 @@ $f_x + t^b + V_1^2 + attach(A, top: alpha, bottom: beta)$ --- -// Test text vs ident parsing. -$pi_1(Y), a_f(x) != a_zeta(x)$ +// Test function call after subscript. +$pi_1(Y), a_f(x), a^zeta(x) \ + a^subset.eq(x), a_(zeta(x)), pi_(1(Y))$ --- // Test associativity and scaling. diff --git a/tests/typ/math/frac.typ b/tests/typ/math/frac.typ index 27c2ae45b..db37e28c2 100644 --- a/tests/typ/math/frac.typ +++ b/tests/typ/math/frac.typ @@ -8,10 +8,6 @@ $ x = 1/2 = a/(a h) = a/a = a/(1/2) $ // Test parenthesis removal. $ (|x| + |y|)/2 < [1+2]/3 $ ---- -// Test associativity. -$ 1/2/3 = (1/2)/3 = 1/(2/3) $ - --- // Test large fraction. $ x = (-b plus.minus sqrt(b^2 - 4a c))/(2a) $ @@ -23,3 +19,14 @@ $ binom(circle, square) $ --- // Error: 8-13 missing argument: lower index $ binom(x^2) $ + +--- +// Test associativity. +$ 1/2/3 = (1/2)/3 = 1/(2/3) $ + +--- +// Test precedence. +$ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \ + 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \ + +[x]/2, 1(x)/2, 2[x]/2 \ + (a)b/2, b(a)[b]/2 $