Show default values in documentation

Fixes #169
Fixes #1102
This commit is contained in:
Laurenz 2023-05-17 14:41:46 +02:00
parent 46aace78ac
commit 551ea99d05
36 changed files with 302 additions and 229 deletions

View File

@ -492,6 +492,7 @@ pub struct ParamModel {
pub example: Option<Html>, pub example: Option<Html>,
pub types: Vec<&'static str>, pub types: Vec<&'static str>,
pub strings: Vec<StrParam>, pub strings: Vec<StrParam>,
pub default: Option<Html>,
pub positional: bool, pub positional: bool,
pub named: bool, pub named: bool,
pub required: bool, pub required: bool,
@ -532,6 +533,10 @@ fn param_model(resolver: &dyn Resolver, info: &ParamInfo) -> ParamModel {
example: example.map(|md| Html::markdown(resolver, md)), example: example.map(|md| Html::markdown(resolver, md)),
types, types,
strings, strings,
default: info.default.map(|default| {
let node = typst::syntax::parse_code(&default().repr());
Html::new(typst::ide::highlight_html(&node))
}),
positional: info.positional, positional: info.positional,
named: info.named, named: info.named,
required: info.required, required: info.required,
@ -721,6 +726,7 @@ fn method_model(resolver: &dyn Resolver, part: &'static str) -> MethodModel {
example: None, example: None,
types, types,
strings: vec![], strings: vec![],
default: None,
positional, positional,
named, named,
required, required,

View File

@ -412,7 +412,7 @@ pub fn tanh(
pub fn log( pub fn log(
/// The number whose logarithm to calculate. Must be strictly positive. /// The number whose logarithm to calculate. Must be strictly positive.
value: Spanned<Num>, value: Spanned<Num>,
/// The base of the logarithm. Defaults to `{10}` and may not be zero. /// The base of the logarithm. May not be zero.
#[named] #[named]
#[default(Spanned::new(10.0, Span::detached()))] #[default(Spanned::new(10.0, Span::detached()))]
base: Spanned<f64>, base: Spanned<f64>,

View File

@ -135,23 +135,18 @@ pub fn rgb(
/// ] /// ]
/// ``` /// ```
#[external] #[external]
#[default]
hex: EcoString, hex: EcoString,
/// The red component. /// The red component.
#[external] #[external]
#[default]
red: Component, red: Component,
/// The green component. /// The green component.
#[external] #[external]
#[default]
green: Component, green: Component,
/// The blue component. /// The blue component.
#[external] #[external]
#[default]
blue: Component, blue: Component,
/// The alpha component. /// The alpha component.
#[external] #[external]
#[default]
alpha: Component, alpha: Component,
) -> Value { ) -> Value {
Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {

View File

@ -58,7 +58,6 @@ pub fn csv(
path: Spanned<EcoString>, path: Spanned<EcoString>,
/// The delimiter that separates columns in the CSV file. /// The delimiter that separates columns in the CSV file.
/// Must be a single ASCII character. /// Must be a single ASCII character.
/// Defaults to a comma.
#[named] #[named]
#[default] #[default]
delimiter: Delimiter, delimiter: Delimiter,
@ -69,7 +68,7 @@ pub fn csv(
let mut builder = csv::ReaderBuilder::new(); let mut builder = csv::ReaderBuilder::new();
builder.has_headers(false); builder.has_headers(false);
builder.delimiter(delimiter.0); builder.delimiter(delimiter.0 as u8);
let mut reader = builder.from_reader(data.as_slice()); let mut reader = builder.from_reader(data.as_slice());
let mut array = Array::new(); let mut array = Array::new();
@ -87,7 +86,7 @@ pub fn csv(
} }
/// The delimiter to use when parsing CSV files. /// The delimiter to use when parsing CSV files.
struct Delimiter(u8); struct Delimiter(char);
cast_from_value! { cast_from_value! {
Delimiter, Delimiter,
@ -102,13 +101,17 @@ cast_from_value! {
Err("delimiter must be an ASCII character")? Err("delimiter must be an ASCII character")?
} }
Self(first as u8) Self(first)
}, },
} }
cast_to_value! {
v: Delimiter => v.0.into()
}
impl Default for Delimiter { impl Default for Delimiter {
fn default() -> Self { fn default() -> Self {
Self(b',') Self(',')
} }
} }
@ -313,7 +316,9 @@ fn format_toml_error(error: toml::de::Error) -> EcoString {
/// } /// }
/// } /// }
/// ///
/// #bookshelf(yaml("scifi-authors.yaml")) /// #bookshelf(
/// yaml("scifi-authors.yaml")
/// )
/// ``` /// ```
/// ///
/// Display: YAML /// Display: YAML
@ -386,15 +391,15 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
/// ///
/// ## Example { #example } /// ## Example { #example }
/// ```example /// ```example
/// #let findChild(elem, tag) = { /// #let find-child(elem, tag) = {
/// elem.children /// elem.children
/// .find(e => "tag" in e and e.tag == tag) /// .find(e => "tag" in e and e.tag == tag)
/// } /// }
/// ///
/// #let article(elem) = { /// #let article(elem) = {
/// let title = findChild(elem, "title") /// let title = find-child(elem, "title")
/// let author = findChild(elem, "author") /// let author = find-child(elem, "author")
/// let pars = findChild(elem, "content") /// let pars = find-child(elem, "content")
/// ///
/// heading(title.children.first()) /// heading(title.children.first())
/// text(10pt, weight: "medium")[ /// text(10pt, weight: "medium")[
@ -411,9 +416,9 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
/// } /// }
/// ///
/// #let data = xml("example.xml") /// #let data = xml("example.xml")
/// #for child in data.first().children { /// #for elem in data.first().children {
/// if (type(child) == "dictionary") { /// if (type(elem) == "dictionary") {
/// article(child) /// article(elem)
/// } /// }
/// } /// }
/// ``` /// ```

View File

@ -229,7 +229,6 @@ pub struct BlockElem {
/// Whether the block can be broken and continue on the next page. /// Whether the block can be broken and continue on the next page.
/// ///
/// Defaults to `{true}`.
/// ```example /// ```example
/// #set page(height: 80pt) /// #set page(height: 80pt)
/// The following block will /// The following block will
@ -282,13 +281,16 @@ pub struct BlockElem {
/// A second paragraph. /// A second paragraph.
/// ``` /// ```
#[external] #[external]
#[default(Em::new(1.2).into())]
pub spacing: Spacing, pub spacing: Spacing,
/// The spacing between this block and its predecessor. Takes precedence /// The spacing between this block and its predecessor. Takes precedence
/// over `spacing`. Can be used in combination with a show rule to adjust /// over `spacing`. Can be used in combination with a show rule to adjust
/// the spacing around arbitrary block-level elements. /// the spacing around arbitrary block-level elements.
/// #[external]
/// The default value is `{1.2em}`. #[default(Em::new(1.2).into())]
pub above: Spacing,
#[internal]
#[parse( #[parse(
let spacing = args.named("spacing")?; let spacing = args.named("spacing")?;
args.named("above")? args.named("above")?
@ -300,8 +302,10 @@ pub struct BlockElem {
/// The spacing between this block and its successor. Takes precedence /// The spacing between this block and its successor. Takes precedence
/// over `spacing`. /// over `spacing`.
/// #[external]
/// The default value is `{1.2em}`. #[default(Em::new(1.2).into())]
pub below: Spacing,
#[internal]
#[parse( #[parse(
args.named("below")? args.named("below")?
.map(VElem::block_around) .map(VElem::block_around)

View File

@ -121,7 +121,6 @@ pub struct EnumElem {
/// Whether to display the full numbering, including the numbers of /// Whether to display the full numbering, including the numbers of
/// all parent enumerations. /// all parent enumerations.
/// ///
/// Defaults to `{false}`.
/// ///
/// ```example /// ```example
/// #set enum(numbering: "1.a)", full: true) /// #set enum(numbering: "1.a)", full: true)

View File

@ -66,8 +66,6 @@ pub struct ListElem {
/// control, you may pass a function that maps the list's nesting depth /// control, you may pass a function that maps the list's nesting depth
/// (starting from `{0}`) to a desired marker. /// (starting from `{0}`) to a desired marker.
/// ///
/// Default: `•`
///
/// ```example /// ```example
/// #set list(marker: [--]) /// #set list(marker: [--])
/// - A more classic list /// - A more classic list
@ -79,7 +77,7 @@ pub struct ListElem {
/// - Items /// - Items
/// - Items /// - Items
/// ``` /// ```
#[default(ListMarker::Content(vec![]))] #[default(ListMarker::Content(vec![TextElem::packed('•')]))]
pub marker: ListMarker, pub marker: ListMarker,
/// The indent of each item. /// The indent of each item.
@ -192,11 +190,9 @@ impl ListMarker {
/// Resolve the marker for the given depth. /// Resolve the marker for the given depth.
fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult<Content> { fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult<Content> {
Ok(match self { Ok(match self {
Self::Content(list) => list Self::Content(list) => {
.get(depth) list.get(depth).or(list.last()).cloned().unwrap_or_default()
.or(list.last()) }
.cloned()
.unwrap_or_else(|| TextElem::packed('•')),
Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(), Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(),
}) })
} }
@ -216,7 +212,11 @@ cast_from_value! {
cast_to_value! { cast_to_value! {
v: ListMarker => match v { v: ListMarker => match v {
ListMarker::Content(vec) => vec.into(), ListMarker::Content(vec) => if vec.len() == 1 {
vec.into_iter().next().unwrap().into()
} else {
vec.into()
},
ListMarker::Func(func) => func.into(), ListMarker::Func(func) => func.into(),
} }
} }

View File

@ -26,9 +26,9 @@ use crate::prelude::*;
/// Category: layout /// Category: layout
#[element] #[element]
pub struct PageElem { pub struct PageElem {
/// A standard paper size to set width and height. When this is not /// A standard paper size to set width and height.
/// specified, Typst defaults to `{"a4"}` paper.
#[external] #[external]
#[default(Paper::A4)]
pub paper: Paper, pub paper: Paper,
/// The width of the page. /// The width of the page.
@ -470,6 +470,8 @@ cast_to_value! {
/// Specification of a paper. /// Specification of a paper.
#[derive(Debug, Copy, Clone, Hash)] #[derive(Debug, Copy, Clone, Hash)]
pub struct Paper { pub struct Paper {
/// The name of the paper.
name: &'static str,
/// The width of the paper in millimeters. /// The width of the paper in millimeters.
width: Scalar, width: Scalar,
/// The height of the paper in millimeters. /// The height of the paper in millimeters.
@ -490,12 +492,13 @@ impl Paper {
/// Defines paper constants and a paper parsing implementation. /// Defines paper constants and a paper parsing implementation.
macro_rules! papers { macro_rules! papers {
($(($var:ident: $width:expr, $height: expr, $pat:literal))*) => { ($(($var:ident: $width:expr, $height: expr, $name:literal))*) => {
/// Predefined papers. /// Predefined papers.
/// ///
/// Each paper is parsable from its name in kebab-case. /// Each paper is parsable from its name in kebab-case.
impl Paper { impl Paper {
$(pub const $var: Self = Self { $(pub const $var: Self = Self {
name: $name,
width: Scalar($width), width: Scalar($width),
height: Scalar($height), height: Scalar($height),
};)* };)*
@ -506,7 +509,7 @@ macro_rules! papers {
fn from_str(name: &str) -> Result<Self, Self::Err> { fn from_str(name: &str) -> Result<Self, Self::Err> {
match name.to_lowercase().as_str() { match name.to_lowercase().as_str() {
$($pat => Ok(Self::$var),)* $($name => Ok(Self::$var),)*
_ => Err("unknown paper size"), _ => Err("unknown paper size"),
} }
} }
@ -516,9 +519,13 @@ macro_rules! papers {
Paper, Paper,
$( $(
/// Produces a paper of the respective size. /// Produces a paper of the respective size.
$pat => Self::$var, $name => Self::$var,
)* )*
} }
cast_to_value! {
v: Paper => v.name.into()
}
}; };
} }

View File

@ -20,8 +20,11 @@ use crate::text::{
/// ///
/// ## Example { #example } /// ## Example { #example }
/// ```example /// ```example
/// #set par(first-line-indent: 1em, justify: true)
/// #show par: set block(spacing: 0.65em) /// #show par: set block(spacing: 0.65em)
/// #set par(
/// first-line-indent: 1em,
/// justify: true,
/// )
/// ///
/// We proceed by contradiction. /// We proceed by contradiction.
/// Suppose that there exists a set /// Suppose that there exists a set
@ -40,8 +43,6 @@ use crate::text::{
#[element(Construct)] #[element(Construct)]
pub struct ParElem { pub struct ParElem {
/// The spacing between lines. /// The spacing between lines.
///
/// The default value is `{0.65em}`.
#[resolve] #[resolve]
#[default(Em::new(0.65).into())] #[default(Em::new(0.65).into())]
pub leading: Length, pub leading: Length,
@ -102,6 +103,7 @@ pub struct ParElem {
/// The contents of the paragraph. /// The contents of the paragraph.
#[external] #[external]
#[required]
pub body: Content, pub body: Content,
/// The paragraph's children. /// The paragraph's children.

View File

@ -104,16 +104,18 @@ pub struct TableElem {
/// How to stroke the cells. /// How to stroke the cells.
/// ///
/// This can be a color, a stroke width, both, or `{none}` to disable /// See the [line's documentation]($func/line.stroke) for more details.
/// the stroke. /// Strokes can be disabled by setting this to `{none}`.
///
/// _Note:_ Richer stroke customization for individual cells is not yet
/// implemented, but will be in the future. In the meantime, you can use
/// the third-party [tablex library](https://github.com/PgBiel/typst-tablex/).
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Some(PartialStroke::default()))] #[default(Some(PartialStroke::default()))]
pub stroke: Option<PartialStroke>, pub stroke: Option<PartialStroke>,
/// How much to pad the cells's content. /// How much to pad the cells's content.
///
/// The default value is `{5pt}`.
#[default(Abs::pt(5.0).into())] #[default(Abs::pt(5.0).into())]
pub inset: Rel<Length>, pub inset: Rel<Length>,

View File

@ -83,10 +83,9 @@ pub struct RotateElem {
/// The origin of the rotation. /// The origin of the rotation.
/// ///
/// By default, the origin is the center of the rotated element. If, /// If, for instance, you wanted the bottom left corner of the rotated
/// however, you wanted the bottom left corner of the rotated element to /// element to stay aligned with the baseline, you would set it to `bottom +
/// stay aligned with the baseline, you would set the origin to `bottom + /// left` instead.
/// left`.
/// ///
/// ```example /// ```example
/// #set text(spacing: 8pt) /// #set text(spacing: 8pt)
@ -98,6 +97,8 @@ pub struct RotateElem {
/// #box(rotate(30deg, origin: bottom + right, square())) /// #box(rotate(30deg, origin: bottom + right, square()))
/// ``` /// ```
#[resolve] #[resolve]
#[fold]
#[default(Align::CENTER_HORIZON)]
pub origin: Axes<Option<GenAlign>>, pub origin: Axes<Option<GenAlign>>,
/// The content to rotate. /// The content to rotate.
@ -115,8 +116,8 @@ impl Layout for RotateElem {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON); let Axes { x, y } =
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s));
let ts = Transform::translate(x, y) let ts = Transform::translate(x, y)
.pre_concat(Transform::rotate(self.angle(styles))) .pre_concat(Transform::rotate(self.angle(styles)))
.pre_concat(Transform::translate(-x, -y)); .pre_concat(Transform::translate(-x, -y));
@ -160,13 +161,13 @@ pub struct ScaleElem {
/// The origin of the transformation. /// The origin of the transformation.
/// ///
/// By default, the origin is the center of the scaled element.
///
/// ```example /// ```example
/// A#box(scale(75%)[A])A \ /// A#box(scale(75%)[A])A \
/// B#box(scale(75%, origin: bottom + left)[B])B /// B#box(scale(75%, origin: bottom + left)[B])B
/// ``` /// ```
#[resolve] #[resolve]
#[fold]
#[default(Align::CENTER_HORIZON)]
pub origin: Axes<Option<GenAlign>>, pub origin: Axes<Option<GenAlign>>,
/// The content to scale. /// The content to scale.
@ -184,8 +185,8 @@ impl Layout for ScaleElem {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON); let Axes { x, y } =
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y) let transform = Transform::translate(x, y)
.pre_concat(Transform::scale(self.x(styles), self.y(styles))) .pre_concat(Transform::scale(self.x(styles), self.y(styles)))
.pre_concat(Transform::translate(-x, -y)); .pre_concat(Transform::translate(-x, -y));

View File

@ -24,8 +24,6 @@ pub struct CancelElem {
/// the whole element being "cancelled". A value of `{100%}` would then have /// the whole element being "cancelled". A value of `{100%}` would then have
/// the line span precisely the element's diagonal. /// the line span precisely the element's diagonal.
/// ///
/// Defaults to `{100% + 3pt}`.
///
/// ```example /// ```example
/// >>> #set page(width: 140pt) /// >>> #set page(width: 140pt)
/// $ a + cancel(x, length: #200%) /// $ a + cancel(x, length: #200%)
@ -37,8 +35,6 @@ pub struct CancelElem {
/// If the cancel line should be inverted (pointing to the top left instead /// If the cancel line should be inverted (pointing to the top left instead
/// of top right). /// of top right).
/// ///
/// Defaults to `{false}`.
///
/// ```example /// ```example
/// >>> #set page(width: 140pt) /// >>> #set page(width: 140pt)
/// $ (a cancel((b + c), inverted: #true)) / /// $ (a cancel((b + c), inverted: #true)) /
@ -50,8 +46,6 @@ pub struct CancelElem {
/// If two opposing cancel lines should be drawn, forming a cross over the /// If two opposing cancel lines should be drawn, forming a cross over the
/// element. Overrides `inverted`. /// element. Overrides `inverted`.
/// ///
/// Defaults to `{false}`.
///
/// ```example /// ```example
/// >>> #set page(width: 140pt) /// >>> #set page(width: 140pt)
/// $ cancel(Pi, cross: #true) $ /// $ cancel(Pi, cross: #true) $
@ -85,6 +79,11 @@ pub struct CancelElem {
/// ``` /// ```
#[resolve] #[resolve]
#[fold] #[fold]
#[default(PartialStroke {
// Default stroke has 0.5pt for better visuals.
thickness: Smart::Custom(Abs::pt(0.5)),
..Default::default()
})]
pub stroke: PartialStroke, pub stroke: PartialStroke,
} }
@ -100,10 +99,8 @@ impl LayoutMath for CancelElem {
let span = self.span(); let span = self.span();
let length = self.length(styles).resolve(styles); let length = self.length(styles).resolve(styles);
// Default stroke has 0.5pt for better visuals.
let stroke = self.stroke(styles).unwrap_or(Stroke { let stroke = self.stroke(styles).unwrap_or(Stroke {
paint: TextElem::fill_in(styles), paint: TextElem::fill_in(styles),
thickness: Abs::pt(0.5),
..Default::default() ..Default::default()
}); });

View File

@ -19,8 +19,6 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
#[element(LayoutMath)] #[element(LayoutMath)]
pub struct LrElem { pub struct LrElem {
/// The size of the brackets, relative to the height of the wrapped content. /// The size of the brackets, relative to the height of the wrapped content.
///
/// Defaults to `{100%}`.
pub size: Smart<Rel<Length>>, pub size: Smart<Rel<Length>>,
/// The delimited content, including the delimiters. /// The delimited content, including the delimiters.

View File

@ -27,8 +27,6 @@ pub struct OpElem {
pub text: EcoString, pub text: EcoString,
/// Whether the operator should force attachments to display as limits. /// Whether the operator should force attachments to display as limits.
///
/// Defaults to `{false}`.
#[default(false)] #[default(false)]
pub limits: bool, pub limits: bool,
} }

View File

@ -144,8 +144,6 @@ pub struct FigureElem {
/// How to number the figure. Accepts a /// How to number the figure. Accepts a
/// [numbering pattern or function]($func/numbering). /// [numbering pattern or function]($func/numbering).
///
/// Defaults to `{"1"}`.
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))] #[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
pub numbering: Option<Numbering>, pub numbering: Option<Numbering>,
@ -155,8 +153,6 @@ pub struct FigureElem {
/// Whether the figure should appear in an [`outline`]($func/outline) /// Whether the figure should appear in an [`outline`]($func/outline)
/// of figures. /// of figures.
///
/// Defaults to `{true}`.
#[default(true)] #[default(true)]
pub outlined: bool, pub outlined: bool,

View File

@ -127,7 +127,7 @@ pub struct OutlineElem {
pub indent: bool, pub indent: bool,
/// Content to fill the space between the title and the page number. Can be /// Content to fill the space between the title and the page number. Can be
/// set to `none` to disable filling. The default is `{repeat[.]}`. /// set to `none` to disable filling.
/// ///
/// ```example /// ```example
/// #outline(fill: line(length: 100%)) /// #outline(fill: line(length: 100%))

View File

@ -98,7 +98,7 @@ use crate::prelude::*;
/// ///
/// Display: Query /// Display: Query
/// Category: meta /// Category: meta
/// Returns: content /// Returns: array
#[func] #[func]
pub fn query( pub fn query(
/// Can be an element function like a `heading` or `figure`, a `{<label>}` /// Can be an element function like a `heading` or `figure`, a `{<label>}`

View File

@ -15,8 +15,11 @@ use crate::prelude::*;
/// Category: text /// Category: text
#[element(Show)] #[element(Show)]
pub struct UnderlineElem { pub struct UnderlineElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line.
/// font tables if `{auto}`. ///
/// See the [line's documentation]($func/line.stroke) for more details. If
/// set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
/// ///
/// ```example /// ```example
/// Take #underline( /// Take #underline(
@ -89,8 +92,11 @@ impl Show for UnderlineElem {
/// Category: text /// Category: text
#[element(Show)] #[element(Show)]
pub struct OverlineElem { pub struct OverlineElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line.
/// font tables if `{auto}`. ///
/// See the [line's documentation]($func/line.stroke) for more details. If
/// set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
/// ///
/// ```example /// ```example
/// #set text(fill: olive) /// #set text(fill: olive)
@ -169,8 +175,11 @@ impl Show for OverlineElem {
/// Category: text /// Category: text
#[element(Show)] #[element(Show)]
pub struct StrikeElem { pub struct StrikeElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line.
/// font tables if `{auto}`. ///
/// See the [line's documentation]($func/line.stroke) for more details. If
/// set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
/// ///
/// _Note:_ Please don't use this for real redaction as you can still /// _Note:_ Please don't use this for real redaction as you can still
/// copy paste the text. /// copy paste the text.

View File

@ -573,7 +573,11 @@ cast_from_value! {
} }
cast_to_value! { cast_to_value! {
v: FontList => v.0.into() v: FontList => if v.0.len() == 1 {
v.0.into_iter().next().unwrap().0.into()
} else {
v.0.into()
}
} }
/// The size of text. /// The size of text.

View File

@ -27,8 +27,11 @@ pub struct PathElem {
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule). /// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
pub fill: Option<Paint>, pub fill: Option<Paint>,
/// How to stroke the path. See the /// How to stroke the path. This can be:
/// [polygon's documentation]($func/polygon.stroke) for more details. ///
/// See the [line's documentation]($func/line.stroke) for more details. Can
/// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
/// `{1pt}` black if and if only if no fill is given.
#[resolve] #[resolve]
#[fold] #[fold]
pub stroke: Smart<Option<PartialStroke>>, pub stroke: Smart<Option<PartialStroke>>,

View File

@ -29,15 +29,9 @@ pub struct PolygonElem {
/// How to stroke the polygon. This can be: /// How to stroke the polygon. This can be:
/// ///
/// - `{none}` to disable the stroke. /// See the [line's documentation]($func/line.stroke) for more details. Can
/// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is /// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
/// given. /// `{1pt}` black if and if only if no fill is given.
/// - A length specifying the stroke's thickness. The color is inherited,
/// defaulting to black.
/// - A color to use for the stroke. The thickness is inherited, defaulting
/// to `{1pt}`.
/// - A stroke combined from color and thickness using the `+` operator as
/// in `{2pt + red}`.
#[resolve] #[resolve]
#[fold] #[fold]
pub stroke: Smart<Option<PartialStroke>>, pub stroke: Smart<Option<PartialStroke>>,

View File

@ -38,32 +38,13 @@ pub struct RectElem {
/// How to stroke the rectangle. This can be: /// How to stroke the rectangle. This can be:
/// ///
/// - `{none}` to disable the stroke. /// - `{none}` to disable stroking
/// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is /// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
/// given. /// given.
/// - A length specifying the stroke's thickness. The color is inherited, /// - Any kind of stroke that can also be used for
/// defaulting to black. /// [lines]($func/line.stroke).
/// - A color to use for the stroke. The thickness is inherited, defaulting /// - A dictionary describing the stroke for each side inidvidually. The
/// to `{1pt}`. /// dictionary can contain the following keys in order of precedence:
/// - A stroke combined from color and thickness using the `+` operator as
/// in `{2pt + red}`.
/// - A stroke described by a dictionary with any of the following keys:
/// - `color`: the color to use for the stroke
/// - `thickness`: the stroke's thickness
/// - `cap`: one of `"butt"`, `"round"` or `"square"`, the line cap of the stroke
/// - `join`: one of `"miter"`, `"round"` or `"bevel"`, the line join of the stroke
/// - `miter-limit`: the miter limit to use if `join` is `"miter"`, defaults to 4.0
/// - `dash`: the dash pattern to use. Can be any of the following:
/// - One of the strings `"solid"`, `"dotted"`, `"densely-dotted"`, `"loosely-dotted"`,
/// `"dashed"`, `"densely-dashed"`, `"loosely-dashed"`, `"dash-dotted"`,
/// `"densely-dash-dotted"` or `"loosely-dash-dotted"`
/// - An array with elements that specify the lengths of dashes and gaps, alternating.
/// Elements can also be the string `"dot"` for a length equal to the line thickness.
/// - A dict with the keys `array`, same as the array above, and `phase`, the offset to
/// the start of the first dash.
/// - Another dictionary describing the stroke for each side inidvidually.
/// The dictionary can contain the following keys in order
/// of precedence:
/// - `top`: The top stroke. /// - `top`: The top stroke.
/// - `right`: The right stroke. /// - `right`: The right stroke.
/// - `bottom`: The bottom stroke. /// - `bottom`: The bottom stroke.
@ -126,8 +107,6 @@ pub struct RectElem {
/// How much to pad the rectangle's content. /// How much to pad the rectangle's content.
/// ///
/// The default value is `{5pt}`.
///
/// _Note:_ When the rectangle contains text, its exact size depends on the /// _Note:_ When the rectangle contains text, its exact size depends on the
/// current [text edges]($func/text.top-edge). /// current [text edges]($func/text.top-edge).
/// ///
@ -242,8 +221,6 @@ pub struct SquareElem {
/// How much to pad the square's content. See the [rectangle's /// How much to pad the square's content. See the [rectangle's
/// documentation]($func/rect.inset) for more details. /// documentation]($func/rect.inset) for more details.
///
/// The default value is `{5pt}`.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Abs::pt(5.0).into()))]
@ -326,8 +303,6 @@ pub struct EllipseElem {
/// How much to pad the ellipse's content. See the [rectangle's /// How much to pad the ellipse's content. See the [rectangle's
/// documentation]($func/rect.inset) for more details. /// documentation]($func/rect.inset) for more details.
///
/// The default value is `{5pt}`.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Abs::pt(5.0).into()))]
@ -436,8 +411,6 @@ pub struct CircleElem {
/// How much to pad the circle's content. See the [rectangle's /// How much to pad the circle's content. See the [rectangle's
/// documentation]($func/rect.inset) for more details. /// documentation]($func/rect.inset) for more details.
///
/// The default value is `{5pt}`.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Abs::pt(5.0).into()))]

View File

@ -407,9 +407,29 @@ fn create_vtable_func(element: &Elem) -> TokenStream {
/// Create a parameter info for a field. /// Create a parameter info for a field.
fn create_param_info(field: &Field) -> TokenStream { fn create_param_info(field: &Field) -> TokenStream {
let Field { name, docs, positional, variadic, required, ty, .. } = field; let Field {
name,
docs,
positional,
variadic,
required,
default,
fold,
ty,
output,
..
} = field;
let named = !positional; let named = !positional;
let settable = field.settable(); let settable = field.settable();
let default_ty = if *fold { &output } else { &ty };
let default = quote_option(&settable.then(|| {
quote! {
|| {
let typed: #default_ty = #default;
::typst::eval::Value::from(typed)
}
}
}));
let ty = if *variadic { let ty = if *variadic {
quote! { <#ty as ::typst::eval::Variadics>::Inner } quote! { <#ty as ::typst::eval::Variadics>::Inner }
} else { } else {
@ -422,6 +442,7 @@ fn create_param_info(field: &Field) -> TokenStream {
cast: <#ty as ::typst::eval::Cast< cast: <#ty as ::typst::eval::Cast<
::typst::syntax::Spanned<::typst::eval::Value> ::typst::syntax::Spanned<::typst::eval::Value>
>>::describe(), >>::describe(),
default: #default,
positional: #positional, positional: #positional,
named: #named, named: #named,
variadic: #variadic, variadic: #variadic,

View File

@ -152,6 +152,14 @@ fn create_param_info(param: &Param) -> TokenStream {
let Param { name, docs, named, variadic, ty, default, .. } = param; let Param { name, docs, named, variadic, ty, default, .. } = param;
let positional = !named; let positional = !named;
let required = default.is_none(); let required = default.is_none();
let default = quote_option(&default.as_ref().map(|_default| {
quote! {
|| {
let typed: #ty = #default;
::typst::eval::Value::from(typed)
}
}
}));
let ty = if *variadic { let ty = if *variadic {
quote! { <#ty as ::typst::eval::Variadics>::Inner } quote! { <#ty as ::typst::eval::Variadics>::Inner }
} else { } else {
@ -164,6 +172,7 @@ fn create_param_info(param: &Param) -> TokenStream {
cast: <#ty as ::typst::eval::Cast< cast: <#ty as ::typst::eval::Cast<
::typst::syntax::Spanned<::typst::eval::Value> ::typst::syntax::Spanned<::typst::eval::Value>
>>::describe(), >>::describe(),
default: #default,
positional: #positional, positional: #positional,
named: #named, named: #named,
variadic: #variadic, variadic: #variadic,

View File

@ -230,6 +230,12 @@ impl<T: Cast> Cast for Option<T> {
} }
} }
impl<T: Into<Value>> From<Spanned<T>> for Value {
fn from(spanned: Spanned<T>) -> Self {
spanned.v.into()
}
}
impl<T: Into<Value>> From<Option<T>> for Value { impl<T: Into<Value>> From<Option<T>> for Value {
fn from(v: Option<T>) -> Self { fn from(v: Option<T>) -> Self {
match v { match v {

View File

@ -279,6 +279,8 @@ pub struct ParamInfo {
pub docs: &'static str, pub docs: &'static str,
/// Valid values for the parameter. /// Valid values for the parameter.
pub cast: CastInfo, pub cast: CastInfo,
/// Creates an instance of the parameter's default value.
pub default: Option<fn() -> Value>,
/// Is the parameter positional? /// Is the parameter positional?
pub positional: bool, pub positional: bool,
/// Is the parameter named? /// Is the parameter named?

View File

@ -54,7 +54,7 @@ pub enum Value {
Styles(Styles), Styles(Styles),
/// An array of values: `(1, "hi", 12cm)`. /// An array of values: `(1, "hi", 12cm)`.
Array(Array), Array(Array),
/// A dictionary value: `(color: #f79143, pattern: dashed)`. /// A dictionary value: `(a: 1, b: "hi")`.
Dict(Dict), Dict(Dict),
/// An executable function. /// An executable function.
Func(Func), Func(Func),

View File

@ -146,6 +146,10 @@ cast_from_value! {
aligns: Axes<GenAlign> => aligns.map(Some), aligns: Axes<GenAlign> => aligns.map(Some),
} }
cast_to_value! {
v: Axes<Align> => v.map(GenAlign::from).into()
}
cast_to_value! { cast_to_value! {
v: Axes<Option<GenAlign>> => match (v.x, v.y) { v: Axes<Option<GenAlign>> => match (v.x, v.y) {
(Some(x), Some(y)) => Axes::new(x, y).into(), (Some(x), Some(y)) => Axes::new(x, y).into(),
@ -196,6 +200,14 @@ impl Fold for GenAlign {
} }
} }
impl Fold for Align {
type Output = Self;
fn fold(self, _: Self::Output) -> Self::Output {
self
}
}
/// Utility struct to restrict a passed alignment value to the horizontal axis /// Utility struct to restrict a passed alignment value to the horizontal axis
/// on cast. /// on cast.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]

View File

@ -141,9 +141,9 @@ where
{ {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Axes { x: Some(x), y: Some(y) } = if let Axes { x: Some(x), y: Some(y) } =
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>()) self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<GenAlign>())
{ {
write!(f, "{:?}-{:?}", x, y) write!(f, "{:?} + {:?}", x, y)
} else if (&self.x as &dyn Any).is::<Abs>() { } else if (&self.x as &dyn Any).is::<Abs>() {
write!(f, "Size({:?}, {:?})", self.x, self.y) write!(f, "Size({:?}, {:?})", self.x, self.y)
} else { } else {

View File

@ -117,9 +117,21 @@ where
} }
fn cast(mut value: Value) -> StrResult<Self> { fn cast(mut value: Value) -> StrResult<Self> {
if let Value::Dict(dict) = &mut value { let keys = [
let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); "top-left",
"top-right",
"bottom-right",
"bottom-left",
"left",
"top",
"right",
"bottom",
"rest",
];
if let Value::Dict(dict) = &mut value {
if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) {
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
let rest = take("rest")?; let rest = take("rest")?;
let left = take("left")?.or_else(|| rest.clone()); let left = take("left")?.or_else(|| rest.clone());
let top = take("top")?.or_else(|| rest.clone()); let top = take("top")?.or_else(|| rest.clone());
@ -140,20 +152,12 @@ where
.or_else(|| left.clone()), .or_else(|| left.clone()),
}; };
dict.finish(&[ dict.finish(&keys)?;
"top-left", return Ok(corners);
"top-right", }
"bottom-right", }
"bottom-left",
"left",
"top",
"right",
"bottom",
"rest",
])?;
Ok(corners) if T::is(&value) {
} else if T::is(&value) {
Ok(Self::splat(Some(T::cast(value)?))) Ok(Self::splat(Some(T::cast(value)?)))
} else { } else {
<Self as Cast>::error(value) <Self as Cast>::error(value)
@ -184,30 +188,27 @@ impl<T: Fold> Fold for Corners<Option<T>> {
} }
} }
impl<T> From<Corners<Option<T>>> for Value impl<T> From<Corners<T>> for Value
where where
T: PartialEq + Into<Value>, T: PartialEq + Into<Value>,
{ {
fn from(corners: Corners<Option<T>>) -> Self { fn from(corners: Corners<T>) -> Self {
if corners.is_uniform() { if corners.is_uniform() {
if let Some(value) = corners.top_left { return corners.top_left.into();
return value.into();
}
} }
let mut dict = Dict::new(); let mut dict = Dict::new();
if let Some(top_left) = corners.top_left { let mut handle = |key: &str, component: T| {
dict.insert("top-left".into(), top_left.into()); let value = component.into();
} if value != Value::None {
if let Some(top_right) = corners.top_right { dict.insert(key.into(), value);
dict.insert("top-right".into(), top_right.into());
}
if let Some(bottom_right) = corners.bottom_right {
dict.insert("bottom-right".into(), bottom_right.into());
}
if let Some(bottom_left) = corners.bottom_left {
dict.insert("bottom-left".into(), bottom_left.into());
} }
};
handle("top-left", corners.top_left);
handle("top-right", corners.top_right);
handle("bottom-right", corners.bottom_right);
handle("bottom-left", corners.bottom_left);
Value::Dict(dict) Value::Dict(dict)
} }

View File

@ -227,3 +227,7 @@ impl Fold for Rel<Length> {
self self
} }
} }
cast_to_value! {
v: Rel<Abs> => v.map(Length::from).into()
}

View File

@ -187,10 +187,10 @@ where
} }
fn cast(mut value: Value) -> StrResult<Self> { fn cast(mut value: Value) -> StrResult<Self> {
let keys = ["left", "top", "right", "bottom", "x", "y", "rest"];
if let Value::Dict(dict) = &mut value { if let Value::Dict(dict) = &mut value {
let mut try_cast = || -> StrResult<_> { if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) {
let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
let rest = take("rest")?; let rest = take("rest")?;
let x = take("x")?.or_else(|| rest.clone()); let x = take("x")?.or_else(|| rest.clone());
let y = take("y")?.or_else(|| rest.clone()); let y = take("y")?.or_else(|| rest.clone());
@ -201,13 +201,8 @@ where
bottom: take("bottom")?.or_else(|| y.clone()), bottom: take("bottom")?.or_else(|| y.clone()),
}; };
dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; dict.finish(&keys)?;
return Ok(sides);
Ok(sides)
};
if let Ok(res) = try_cast() {
return Ok(res);
} }
} }
@ -223,35 +218,31 @@ where
} }
} }
impl<T> From<Sides<Option<T>>> for Value impl<T> From<Sides<T>> for Value
where where
T: PartialEq + Into<Value>, T: PartialEq + Into<Value>,
{ {
fn from(sides: Sides<Option<T>>) -> Self { fn from(sides: Sides<T>) -> Self {
if sides.is_uniform() { if sides.is_uniform() {
if let Some(value) = sides.left { return sides.left.into();
return value.into();
}
} }
let mut dict = Dict::new(); let mut dict = Dict::new();
if let Some(left) = sides.left { let mut handle = |key: &str, component: T| {
dict.insert("left".into(), left.into()); let value = component.into();
} if value != Value::None {
if let Some(top) = sides.top { dict.insert(key.into(), value);
dict.insert("top".into(), top.into());
}
if let Some(right) = sides.right {
dict.insert("right".into(), right.into());
}
if let Some(bottom) = sides.bottom {
dict.insert("bottom".into(), bottom.into());
} }
};
handle("left", sides.left);
handle("top", sides.top);
handle("right", sides.right);
handle("bottom", sides.bottom);
Value::Dict(dict) Value::Dict(dict)
} }
} }
impl<T: Resolve> Resolve for Sides<T> { impl<T: Resolve> Resolve for Sides<T> {
type Output = Sides<T::Output>; type Output = Sides<T::Output>;

View File

@ -51,6 +51,35 @@ pub struct PartialStroke<T = Length> {
pub miter_limit: Smart<Scalar>, pub miter_limit: Smart<Scalar>,
} }
impl<T> PartialStroke<T> {
/// Map the contained lengths with `f`.
pub fn map<F, U>(self, f: F) -> PartialStroke<U>
where
F: Fn(T) -> U,
{
PartialStroke {
paint: self.paint,
thickness: self.thickness.map(&f),
line_cap: self.line_cap,
line_join: self.line_join,
dash_pattern: self.dash_pattern.map(|pattern| {
pattern.map(|pattern| DashPattern {
array: pattern
.array
.into_iter()
.map(|l| match l {
DashLength::Length(v) => DashLength::Length(f(v)),
DashLength::LineWidth => DashLength::LineWidth,
})
.collect(),
phase: f(pattern.phase),
})
}),
miter_limit: self.miter_limit,
}
}
}
impl PartialStroke<Abs> { impl PartialStroke<Abs> {
/// Unpack the stroke, filling missing fields from the `default`. /// Unpack the stroke, filling missing fields from the `default`.
pub fn unwrap_or(self, default: Stroke) -> Stroke { pub fn unwrap_or(self, default: Stroke) -> Stroke {
@ -106,13 +135,13 @@ impl<T: Debug> Debug for PartialStroke<T> {
} }
(Smart::Custom(paint), Smart::Auto) => paint.fmt(f), (Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
(Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
(Smart::Auto, Smart::Auto) => f.pad("<stroke>"), (Smart::Auto, Smart::Auto) => f.pad("1pt + black"),
} }
} else { } else {
write!(f, "(")?; write!(f, "(")?;
let mut sep = ""; let mut sep = "";
if let Smart::Custom(paint) = &paint { if let Smart::Custom(paint) = &paint {
write!(f, "{}color: {:?}", sep, paint)?; write!(f, "{}paint: {:?}", sep, paint)?;
sep = ", "; sep = ", ";
} }
if let Smart::Custom(thickness) = &thickness { if let Smart::Custom(thickness) = &thickness {
@ -176,7 +205,7 @@ impl Debug for LineJoin {
} }
} }
/// A line dash pattern /// A line dash pattern.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct DashPattern<T = Length, DT = DashLength<T>> { pub struct DashPattern<T = Length, DT = DashLength<T>> {
/// The dash array. /// The dash array.
@ -293,20 +322,12 @@ impl Resolve for DashPattern {
cast_from_value! { cast_from_value! {
PartialStroke: "stroke", PartialStroke: "stroke",
thickness: Length => Self { thickness: Length => Self {
paint: Smart::Auto,
thickness: Smart::Custom(thickness), thickness: Smart::Custom(thickness),
line_cap: Smart::Auto, ..Default::default()
line_join: Smart::Auto,
dash_pattern: Smart::Auto,
miter_limit: Smart::Auto,
}, },
color: Color => Self { color: Color => Self {
paint: Smart::Custom(color.into()), paint: Smart::Custom(color.into()),
thickness: Smart::Auto, ..Default::default()
line_cap: Smart::Auto,
line_join: Smart::Auto,
dash_pattern: Smart::Auto,
miter_limit: Smart::Auto,
}, },
mut dict: Dict => { mut dict: Dict => {
fn take<T: Cast<Value>>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> { fn take<T: Cast<Value>>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> {
@ -320,7 +341,6 @@ cast_from_value! {
let line_join = take::<LineJoin>(&mut dict, "join")?; let line_join = take::<LineJoin>(&mut dict, "join")?;
let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?; let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?;
let miter_limit = take::<f64>(&mut dict, "miter-limit")?; let miter_limit = take::<f64>(&mut dict, "miter-limit")?;
dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?; dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?;
Self { Self {
@ -359,7 +379,11 @@ impl Fold for PartialStroke<Abs> {
line_cap: self.line_cap.or(outer.line_cap), line_cap: self.line_cap.or(outer.line_cap),
line_join: self.line_join.or(outer.line_join), line_join: self.line_join.or(outer.line_join),
dash_pattern: self.dash_pattern.or(outer.dash_pattern), dash_pattern: self.dash_pattern.or(outer.dash_pattern),
miter_limit: self.miter_limit, miter_limit: self.miter_limit.or(outer.miter_limit),
} }
} }
} }
cast_to_value! {
v: PartialStroke<Abs> => v.map(Length::from).into()
}

View File

@ -18,7 +18,10 @@ pub fn parse(text: &str) -> SyntaxNode {
/// This is only used for syntax highlighting. /// This is only used for syntax highlighting.
pub fn parse_code(text: &str) -> SyntaxNode { pub fn parse_code(text: &str) -> SyntaxNode {
let mut p = Parser::new(text, 0, LexMode::Code); let mut p = Parser::new(text, 0, LexMode::Code);
code(&mut p, |_| false); let m = p.marker();
p.skip();
code_exprs(&mut p, |_| false);
p.wrap_skipless(m, SyntaxKind::Code);
p.finish().into_iter().next().unwrap() p.finish().into_iter().next().unwrap()
} }
@ -511,8 +514,13 @@ fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) {
} }
} }
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { fn code(p: &mut Parser, stop: impl FnMut(SyntaxKind) -> bool) {
let m = p.marker(); let m = p.marker();
code_exprs(p, stop);
p.wrap(m, SyntaxKind::Code);
}
fn code_exprs(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
while !p.eof() && !stop(p.current()) { while !p.eof() && !stop(p.current()) {
p.stop_at_newline(true); p.stop_at_newline(true);
let prev = p.prev_end(); let prev = p.prev_end();
@ -529,7 +537,6 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
p.unexpected(); p.unexpected();
} }
} }
p.wrap(m, SyntaxKind::Code);
} }
fn code_expr(p: &mut Parser) { fn code_expr(p: &mut Parser) {
@ -1474,10 +1481,14 @@ impl<'s> Parser<'s> {
fn wrap(&mut self, m: Marker, kind: SyntaxKind) { fn wrap(&mut self, m: Marker, kind: SyntaxKind) {
self.unskip(); self.unskip();
self.wrap_skipless(m, kind);
self.skip();
}
fn wrap_skipless(&mut self, m: Marker, kind: SyntaxKind) {
let from = m.0.min(self.nodes.len()); let from = m.0.min(self.nodes.len());
let children = self.nodes.drain(from..).collect(); let children = self.nodes.drain(from..).collect();
self.nodes.push(SyntaxNode::inner(kind, children)); self.nodes.push(SyntaxNode::inner(kind, children));
self.skip();
} }
fn progress(&self, offset: usize) -> bool { fn progress(&self, offset: usize) -> bool {

View File

@ -15,8 +15,7 @@ use comemo::{Prehashed, Track};
use elsa::FrozenVec; use elsa::FrozenVec;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use oxipng::{InFile, Options, OutFile}; use oxipng::{InFile, Options, OutFile};
use rayon::iter::ParallelBridge; use rayon::iter::{ParallelBridge, ParallelIterator};
use rayon::iter::ParallelIterator;
use tiny_skia as sk; use tiny_skia as sk;
use unscanny::Scanner; use unscanny::Scanner;
use walkdir::WalkDir; use walkdir::WalkDir;

View File

@ -8,7 +8,7 @@
#for x in () [Nope] #for x in () [Nope]
// Dictionary is traversed in insertion order. // Dictionary is traversed in insertion order.
// Should output `Age: 2. Name: Typst.`. // Should output `Name: Typst. Age: 2.`.
#for (k, v) in (Name: "Typst", Age: 2) [ #for (k, v) in (Name: "Typst", Age: 2) [
#k: #v. #k: #v.
] ]