summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
17 files changed, 221 insertions, 151 deletions
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);
}