summaryrefslogtreecommitdiff
path: root/library/src/basics
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-21 12:50:33 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-21 12:50:33 +0100
commitba294e2670814243122c9e4f560e0f21a4bec13d (patch)
tree36bdf987dc53729bb4a176713b40e2ca7c08a874 /library/src/basics
parent959df6da3b6e3c5876e9d5627cb2970e32a0a37f (diff)
Split up list type into three separate types and document them
Diffstat (limited to 'library/src/basics')
-rw-r--r--library/src/basics/desc.rs183
-rw-r--r--library/src/basics/enum.rs251
-rw-r--r--library/src/basics/heading.rs4
-rw-r--r--library/src/basics/list.rs316
-rw-r--r--library/src/basics/mod.rs5
5 files changed, 527 insertions, 232 deletions
diff --git a/library/src/basics/desc.rs b/library/src/basics/desc.rs
new file mode 100644
index 00000000..2c764c59
--- /dev/null
+++ b/library/src/basics/desc.rs
@@ -0,0 +1,183 @@
+use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing};
+use crate::prelude::*;
+use crate::text::{SpaceNode, TextNode};
+
+/// # Description List
+/// A list of terms and their descriptions.
+///
+/// Displays a sequence of terms and their descriptions vertically. When the
+/// descriptions span over multiple lines, they use hanging indent to
+/// communicate the visual hierarchy.
+///
+/// ## Syntax
+/// This function also has dedicated syntax: Starting a line with a slash,
+/// followed by a term, a colon and a description creates a description list
+/// item.
+///
+/// ## Example
+/// ```
+/// / Ligature: A merged glyph.
+/// / Kerning: A spacing adjustment
+/// between two adjacent letters.
+/// ```
+///
+/// ## Parameters
+/// - items: Content (positional, variadic)
+/// The descrition list's children.
+///
+/// When using the description list syntax, adjacents items are automatically
+/// collected into description lists, even through constructs like for loops.
+///
+/// ### Example
+/// ```
+/// #for year, product in (
+/// "1978": "TeX",
+/// "1984": "LaTeX",
+/// "2019": "Typst",
+/// ) [/ #product: Born in #year.]
+/// ```
+///
+/// - tight: bool (named)
+/// If this is `{false}`, the items are spaced apart with [description list
+/// spacing](@desc/spacing). If it is `{true}`, they use normal
+/// [leading](@par/leading) instead. This makes the description list more
+/// compact, which can look better if the items are short.
+///
+/// ### Example
+/// ```
+/// / Fact: If a description list has
+/// a lot of text, and maybe other
+/// inline content, it should not be
+/// tight anymore.
+///
+/// / Tip: To make it wide, simply
+/// insert a blank line between the
+/// items.
+/// ```
+///
+/// ## Category
+/// basics
+#[func]
+#[capable(Layout)]
+#[derive(Debug, Hash)]
+pub struct DescNode {
+ /// If true, the items are separated by leading instead of list spacing.
+ pub tight: bool,
+ /// The individual bulleted or numbered items.
+ pub items: StyleVec<DescItem>,
+}
+
+#[node]
+impl DescNode {
+ /// The indentation of each item's term.
+ #[property(resolve)]
+ pub const INDENT: Length = Length::zero();
+
+ /// The hanging indent of the description.
+ ///
+ /// # Example
+ /// ```
+ /// #set desc(hanging-indent: 0pt)
+ /// / Term: This description list
+ /// does not make use of hanging
+ /// indents.
+ /// ```
+ #[property(resolve)]
+ pub const HANGING_INDENT: Length = Em::new(1.0).into();
+
+ /// The spacing between the items of a wide (non-tight) description list.
+ ///
+ /// If set to `{auto}` uses the spacing [below blocks](@block/below).
+ pub const SPACING: Smart<Spacing> = Smart::Auto;
+
+ fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
+ Ok(Self {
+ tight: args.named("tight")?.unwrap_or(true),
+ items: args.all()?.into_iter().collect(),
+ }
+ .pack())
+ }
+
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "tight" => Some(Value::Bool(self.tight)),
+ "items" => {
+ Some(Value::Array(self.items.items().map(|item| item.encode()).collect()))
+ }
+ _ => None,
+ }
+ }
+}
+
+impl Layout for DescNode {
+ fn layout(
+ &self,
+ vt: &mut Vt,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ let indent = styles.get(Self::INDENT);
+ let body_indent = styles.get(Self::HANGING_INDENT);
+ let gutter = if self.tight {
+ styles.get(ParNode::LEADING).into()
+ } else {
+ styles
+ .get(Self::SPACING)
+ .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
+ };
+
+ let mut cells = vec![];
+ for (item, map) in self.items.iter() {
+ let body = Content::sequence(vec![
+ HNode { amount: (-body_indent).into(), weak: false }.pack(),
+ (item.term.clone() + TextNode::packed(':')).strong(),
+ SpaceNode.pack(),
+ item.description.clone(),
+ ]);
+
+ cells.push(Content::empty());
+ cells.push(body.styled_with_map(map.clone()));
+ }
+
+ GridNode {
+ tracks: Axes::with_x(vec![
+ TrackSizing::Relative((indent + body_indent).into()),
+ TrackSizing::Auto,
+ ]),
+ gutter: Axes::with_y(vec![gutter.into()]),
+ cells,
+ }
+ .layout(vt, styles, regions)
+ }
+}
+
+/// A description list item.
+#[derive(Debug, Clone, Hash)]
+pub struct DescItem {
+ /// The term described by the list item.
+ pub term: Content,
+ /// The description of the term.
+ pub description: Content,
+}
+
+impl DescItem {
+ /// Encode the item into a value.
+ fn encode(&self) -> Value {
+ Value::Array(array![
+ Value::Content(self.term.clone()),
+ Value::Content(self.description.clone()),
+ ])
+ }
+}
+
+castable! {
+ DescItem,
+ array: Array => {
+ let mut iter = array.into_iter();
+ let (term, description) = match (iter.next(), iter.next(), iter.next()) {
+ (Some(a), Some(b), None) => (a.cast()?, b.cast()?),
+ _ => Err("array must contain exactly two entries")?,
+ };
+ Self { term, description }
+ },
+}
diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs
new file mode 100644
index 00000000..f0ca217a
--- /dev/null
+++ b/library/src/basics/enum.rs
@@ -0,0 +1,251 @@
+use std::str::FromStr;
+
+use crate::compute::NumberingPattern;
+use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
+use crate::prelude::*;
+use crate::text::TextNode;
+
+/// # Enumeration
+/// An ordered list.
+///
+/// Displays a sequence of items vertically and numbers them consecutively.
+///
+/// ## Syntax
+/// This functions also has dedicated syntax:
+///
+/// - Starting a line with a plus sign creates an automatically numbered
+/// enumeration item.
+/// - Start a line with a number followed by a dot creates an explicitly
+/// numbered enumeration item.
+///
+/// Enumeration items can contain multiple paragraphs and other block-level
+/// content. All content that is indented more than an item's plus sign or dot
+/// becomes part of that item.
+///
+/// ## Example
+/// ```
+/// Automatically numbered:
+/// + Preparations
+/// + Analysis
+/// + Conclusions
+///
+/// Manually numbered:
+/// 2. What is the first step?
+/// 5. I am confused.
+/// + Moving on ...
+///
+/// Function call.
+/// #enum[First][Second]
+/// ```
+///
+/// ## Parameters
+/// - items: Content (positional, variadic)
+/// The enumeration's children.
+///
+/// When using the enum syntax, adjacents items are automatically collected
+/// into enumerations, even through constructs like for loops.
+///
+/// ### Example
+/// ```
+/// #for phase in (
+/// "Launch",
+/// "Orbit",
+/// "Descent",
+/// ) [+ #phase]
+/// ```
+///
+/// - start: NonZeroUsize (named)
+/// Which number to start the enumeration with.
+///
+/// ### Example
+/// ```
+/// #enum(
+/// start: 3,
+/// [Skipping],
+/// [Ahead],
+/// )
+/// ```
+///
+/// - tight: bool (named)
+/// If this is `{false}`, the items are spaced apart with
+/// [enum spacing](@enum/spacing). If it is `{true}`, they use normal
+/// [leading](@par/leading) instead. This makes the enumeration more compact,
+/// which can look better if the items are short.
+///
+/// ### Example
+/// ```
+/// + If an enum has a lot of text, and
+/// maybe other inline content, it
+/// should not be tight anymore.
+///
+/// + To make an enum wide, simply
+/// insert a blank line between the
+/// items.
+/// ```
+///
+/// ## Category
+/// basics
+#[func]
+#[capable(Layout)]
+#[derive(Debug, Hash)]
+pub struct EnumNode {
+ /// If true, the items are separated by leading instead of list spacing.
+ pub tight: bool,
+ /// The individual numbered items.
+ pub items: StyleVec<(Option<NonZeroUsize>, Content)>,
+}
+
+#[node]
+impl EnumNode {
+ /// How to number the enumeration. Accepts a
+ /// [numbering pattern](@numbering).
+ ///
+ /// # Example
+ /// ```
+ /// #set enum(numbering: "(a)")
+ ///
+ /// + Different
+ /// + Numbering
+ /// + Style
+ /// ```
+ #[property(referenced)]
+ pub const NUMBERING: EnumNumbering =
+ EnumNumbering::Pattern(NumberingPattern::from_str("1.").unwrap());
+
+ /// The indentation of each item's label.
+ #[property(resolve)]
+ pub const INDENT: Length = Length::zero();
+
+ /// The space between the numbering and the body of each item.
+ #[property(resolve)]
+ pub const BODY_INDENT: Length = Em::new(0.5).into();
+
+ /// The spacing between the items of a wide (non-tight) enumeration.
+ ///
+ /// If set to `{auto}` uses the spacing [below blocks](@block/below).
+ pub const SPACING: Smart<Spacing> = Smart::Auto;
+
+ fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
+ let mut number: NonZeroUsize =
+ args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
+
+ Ok(Self {
+ tight: args.named("tight")?.unwrap_or(true),
+ items: args
+ .all()?
+ .into_iter()
+ .map(|body| {
+ let item = (Some(number), body);
+ number = number.saturating_add(1);
+ item
+ })
+ .collect(),
+ }
+ .pack())
+ }
+
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "tight" => Some(Value::Bool(self.tight)),
+ "items" => Some(Value::Array(
+ self.items
+ .items()
+ .map(|(number, body)| {
+ Value::Dict(dict! {
+ "number" => match *number {
+ Some(n) => Value::Int(n.get() as i64),
+ None => Value::None,
+ },
+ "body" => Value::Content(body.clone()),
+ })
+ })
+ .collect(),
+ )),
+ _ => None,
+ }
+ }
+}
+
+impl Layout for EnumNode {
+ fn layout(
+ &self,
+ vt: &mut Vt,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ let numbering = styles.get(Self::NUMBERING);
+ let indent = styles.get(Self::INDENT);
+ let body_indent = styles.get(Self::BODY_INDENT);
+ let gutter = if self.tight {
+ styles.get(ParNode::LEADING).into()
+ } else {
+ styles
+ .get(Self::SPACING)
+ .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
+ };
+
+ let mut cells = vec![];
+ let mut number = NonZeroUsize::new(1).unwrap();
+ for ((n, item), map) in self.items.iter() {
+ number = n.unwrap_or(number);
+ let resolved = numbering.resolve(vt, number)?;
+ cells.push(Content::empty());
+ cells.push(resolved.styled_with_map(map.clone()));
+ cells.push(Content::empty());
+ cells.push(item.clone().styled_with_map(map.clone()));
+ number = number.saturating_add(1);
+ }
+
+ GridNode {
+ tracks: Axes::with_x(vec![
+ TrackSizing::Relative(indent.into()),
+ TrackSizing::Auto,
+ TrackSizing::Relative(body_indent.into()),
+ TrackSizing::Auto,
+ ]),
+ gutter: Axes::with_y(vec![gutter.into()]),
+ cells,
+ }
+ .layout(vt, styles, regions)
+ }
+}
+
+/// How to number an enumeration.
+#[derive(Debug, Clone, Hash)]
+pub enum EnumNumbering {
+ /// A pattern with prefix, numbering, lower / upper case and suffix.
+ Pattern(NumberingPattern),
+ /// A closure mapping from an item's number to content.
+ Func(Func, Span),
+}
+
+impl EnumNumbering {
+ /// Resolve the marker based on the number.
+ pub fn resolve(&self, vt: &Vt, number: NonZeroUsize) -> SourceResult<Content> {
+ Ok(match self {
+ Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])),
+ Self::Func(func, span) => {
+ let args = Args::new(*span, [Value::Int(number.get() as i64)]);
+ func.call_detached(vt.world(), args)?.display()
+ }
+ })
+ }
+}
+
+impl Cast<Spanned<Value>> for EnumNumbering {
+ fn is(value: &Spanned<Value>) -> bool {
+ matches!(&value.v, Value::Content(_) | Value::Func(_))
+ }
+
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
+ match value.v {
+ Value::Str(v) => Ok(Self::Pattern(v.parse()?)),
+ Value::Func(v) => Ok(Self::Func(v, value.span)),
+ v => Self::error(v),
+ }
+ }
+
+ fn describe() -> CastInfo {
+ CastInfo::Union(vec![CastInfo::Type("string"), CastInfo::Type("function")])
+ }
+}
diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs
index 12a6ac66..925d23a2 100644
--- a/library/src/basics/heading.rs
+++ b/library/src/basics/heading.rs
@@ -11,8 +11,8 @@ use crate::text::{SpaceNode, TextNode, TextSize};
/// With headings, you can structure your document into sections. Each heading
/// has a _level,_ which starts at one and is unbounded upwards. This level
/// indicates the logical role of the following content (section, subsection,
-/// etc.) Top-level heading indicates a top-level section of the document (not
-/// the document's title).
+/// etc.) A top-level heading indicates a top-level section of the document
+/// (not the document's title).
///
/// Typst can automatically number your headings for you. To enable numbering,
/// specify how you want your headings to be numbered with a [numbering
diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs
index ca60576c..eaba6594 100644
--- a/library/src/basics/list.rs
+++ b/library/src/basics/list.rs
@@ -1,28 +1,67 @@
-use crate::compute::NumberingPattern;
-use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing};
+use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
use crate::prelude::*;
-use crate::text::{SpaceNode, TextNode};
+use crate::text::TextNode;
/// # List
-/// An unordered (bulleted) or ordered (numbered) list.
+/// An unordered list.
+///
+/// Displays a sequence of items vertically, with each item introduced by a
+/// marker.
+///
+/// ## Syntax
+/// This functions also has dedicated syntax: Start a line with a hyphen,
+/// followed by a space to create a list item. A list item can contain multiple
+/// paragraphs and other block-level content. All content that is indented
+/// more than an item's hyphen becomes part of that item.
+///
+/// ## Example
+/// ```
+/// - *Content*
+/// - Basics
+/// - Text
+/// - Math
+/// - Layout
+/// - Visualize
+/// - Meta
+///
+/// - *Compute*
+/// #list(
+/// [Foundations],
+/// [Calculate],
+/// [Create],
+/// [Data Loading],
+/// [Utility],
+/// )
+/// ```
///
/// ## Parameters
/// - items: Content (positional, variadic)
-/// The contents of the list items.
+/// The list's children.
///
-/// - start: NonZeroUsize (named)
-/// Which number to start the enumeration with.
+/// When using the list syntax, adjacents items are automatically collected
+/// into lists, even through constructs like for loops.
+///
+/// ### Example
+/// ```
+/// #for letter in "ABC" [
+/// - Letter #letter
+/// ]
+/// ```
///
/// - tight: bool (named)
-/// Makes the list more compact, if enabled. This looks better if the items
-/// fit into a single line each.
+/// If this is `{false}`, the items are spaced apart with
+/// [list spacing](@list/spacing). If it is `{true}`, they use normal
+/// [leading](@par/leading) instead. This makes the list more compact, which
+/// can look better if the items are short.
///
/// ### Example
/// ```
-/// #show columns.with(2)
-/// #list(tight: true)[Tight][List]
-/// #colbreak()
-/// #list(tight: false)[Wide][List]
+/// - If a list has a lot of text, and
+/// maybe other inline content, it
+/// should not be tight anymore.
+///
+/// - To make a list wide, simply insert
+/// a blank line between the items.
/// ```
///
/// ## Category
@@ -30,88 +69,67 @@ use crate::text::{SpaceNode, TextNode};
#[func]
#[capable(Layout)]
#[derive(Debug, Hash)]
-pub struct ListNode<const L: ListKind = LIST> {
+pub struct ListNode {
/// If true, the items are separated by leading instead of list spacing.
pub tight: bool,
/// The individual bulleted or numbered items.
- pub items: StyleVec<ListItem>,
+ pub items: StyleVec<Content>,
}
-/// An ordered list.
-pub type EnumNode = ListNode<ENUM>;
-
-/// A description list.
-pub type DescNode = ListNode<DESC>;
-
#[node]
-impl<const L: ListKind> ListNode<L> {
- /// How the list is labelled.
+impl ListNode {
+ /// The marker which introduces each element.
+ ///
+ /// # Example
+ /// ```
+ /// #set list(marker: [--])
+ ///
+ /// - A more classic list
+ /// - With en-dashes
+ /// ```
#[property(referenced)]
- pub const LABEL: ListLabel = ListLabel::Default;
- /// The indentation of each item's label.
+ pub const MARKER: Content = TextNode::packed('•');
+
+ /// The indent of each item's marker.
#[property(resolve)]
pub const INDENT: Length = Length::zero();
- /// The space between the label and the body of each item.
+
+ /// The spacing between the marker and the body of each item.
#[property(resolve)]
- pub const BODY_INDENT: Length = Em::new(match L {
- LIST | ENUM => 0.5,
- DESC | _ => 1.0,
- })
- .into();
+ pub const BODY_INDENT: Length = Em::new(0.5).into();
+
/// The spacing between the items of a wide (non-tight) list.
+ ///
+ /// If set to `{auto}` uses the spacing [below blocks](@block/below).
pub const SPACING: Smart<Spacing> = Smart::Auto;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
- let items = match L {
- LIST => args
- .all()?
- .into_iter()
- .map(|body| ListItem::List(Box::new(body)))
- .collect(),
- ENUM => {
- let mut number: NonZeroUsize =
- args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
- args.all()?
- .into_iter()
- .map(|body| {
- let item = ListItem::Enum(Some(number), Box::new(body));
- number = number.saturating_add(1);
- item
- })
- .collect()
- }
- DESC | _ => args
- .all()?
- .into_iter()
- .map(|item| ListItem::Desc(Box::new(item)))
- .collect(),
- };
-
- Ok(Self { tight: args.named("tight")?.unwrap_or(true), items }.pack())
+ Ok(Self {
+ tight: args.named("tight")?.unwrap_or(true),
+ items: args.all()?.into_iter().collect(),
+ }
+ .pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"tight" => Some(Value::Bool(self.tight)),
- "items" => {
- Some(Value::Array(self.items.items().map(|item| item.encode()).collect()))
- }
+ "items" => Some(Value::Array(
+ self.items.items().cloned().map(Value::Content).collect(),
+ )),
_ => None,
}
}
}
-impl<const L: ListKind> Layout for ListNode<L> {
+impl Layout for ListNode {
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let mut cells = vec![];
- let mut number = NonZeroUsize::new(1).unwrap();
-
- let label = styles.get(Self::LABEL);
+ let marker = styles.get(Self::MARKER);
let indent = styles.get(Self::INDENT);
let body_indent = styles.get(Self::BODY_INDENT);
let gutter = if self.tight {
@@ -122,35 +140,12 @@ impl<const L: ListKind> Layout for ListNode<L> {
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
};
+ let mut cells = vec![];
for (item, map) in self.items.iter() {
- if let &ListItem::Enum(Some(n), _) = item {
- number = n;
- }
-
cells.push(Content::empty());
-
- let label = if L == LIST || L == ENUM {
- label.resolve(vt, L, number)?.styled_with_map(map.clone())
- } else {
- Content::empty()
- };
-
- cells.push(label);
+ cells.push(marker.clone());
cells.push(Content::empty());
-
- let body = match &item {
- ListItem::List(body) => body.as_ref().clone(),
- ListItem::Enum(_, body) => body.as_ref().clone(),
- ListItem::Desc(item) => Content::sequence(vec![
- HNode { amount: (-body_indent).into(), weak: false }.pack(),
- (item.term.clone() + TextNode::packed(':')).strong(),
- SpaceNode.pack(),
- item.body.clone(),
- ]),
- };
-
- cells.push(body.styled_with_map(map.clone()));
- number = number.saturating_add(1);
+ cells.push(item.clone().styled_with_map(map.clone()));
}
GridNode {
@@ -166,142 +161,3 @@ impl<const L: ListKind> Layout for ListNode<L> {
.layout(vt, styles, regions)
}
}
-
-/// An item in a list.
-#[capable]
-#[derive(Debug, Clone, Hash)]
-pub enum ListItem {
- /// An item of an unordered list.
- List(Box<Content>),
- /// An item of an ordered list.
- Enum(Option<NonZeroUsize>, Box<Content>),
- /// An item of a description list.
- Desc(Box<DescItem>),
-}
-
-impl ListItem {
- /// What kind of item this is.
- pub fn kind(&self) -> ListKind {
- match self {
- Self::List(_) => LIST,
- Self::Enum { .. } => ENUM,
- Self::Desc { .. } => DESC,
- }
- }
-
- /// Encode the item into a value.
- fn encode(&self) -> Value {
- match self {
- Self::List(body) => Value::Content(body.as_ref().clone()),
- Self::Enum(number, body) => Value::Dict(dict! {
- "number" => match *number {
- Some(n) => Value::Int(n.get() as i64),
- None => Value::None,
- },
- "body" => Value::Content(body.as_ref().clone()),
- }),
- Self::Desc(item) => Value::Dict(dict! {
- "term" => Value::Content(item.term.clone()),
- "body" => Value::Content(item.body.clone()),
- }),
- }
- }
-}
-
-#[node]
-impl ListItem {}
-
-/// A description list item.
-#[derive(Debug, Clone, Hash)]
-pub struct DescItem {
- /// The term described by the list item.
- pub term: Content,
- /// The description of the term.
- pub body: Content,
-}
-
-castable! {
- DescItem,
- mut dict: Dict => {
- let term: Content = dict.take("term")?.cast()?;
- let body: Content = dict.take("body")?.cast()?;
- dict.finish(&["term", "body"])?;
- Self { term, body }
- },
-}
-
-/// How to label a list.
-pub type ListKind = usize;
-
-/// An unordered list.
-pub const LIST: ListKind = 0;
-
-/// An ordered list.
-pub const ENUM: ListKind = 1;
-
-/// A description list.
-pub const DESC: ListKind = 2;
-
-/// How to label a list or enumeration.
-#[derive(Debug, Clone, Hash)]
-pub enum ListLabel {
- /// The default labelling.
- Default,
- /// A pattern with prefix, numbering, lower / upper case and suffix.
- Pattern(NumberingPattern),
- /// Bare content.
- Content(Content),
- /// A closure mapping from an item number to a value.
- Func(Func, Span),
-}
-
-impl ListLabel {
- /// Resolve the label based on the level.
- pub fn resolve(
- &self,
- vt: &Vt,
- kind: ListKind,
- number: NonZeroUsize,
- ) -> SourceResult<Content> {
- Ok(match self {
- Self::Default => match kind {
- LIST => TextNode::packed('•'),
- ENUM => TextNode::packed(format_eco!("{}.", number)),
- DESC | _ => panic!("description lists don't have a label"),
- },
- Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])),
- Self::Content(content) => content.clone(),
- Self::Func(func, span) => {
- let args = Args::new(*span, [Value::Int(number.get() as i64)]);
- func.call_detached(vt.world(), args)?.display()
- }
- })
- }
-}
-
-impl Cast<Spanned<Value>> for ListLabel {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(
- &value.v,
- Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_)
- )
- }
-
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- match value.v {
- Value::None => Ok(Self::Content(Content::empty())),
- Value::Str(v) => Ok(Self::Pattern(v.parse()?)),
- Value::Content(v) => Ok(Self::Content(v)),
- Value::Func(v) => Ok(Self::Func(v, value.span)),
- v => Self::error(v),
- }
- }
-
- fn describe() -> CastInfo {
- CastInfo::Union(vec![
- CastInfo::Type("string"),
- CastInfo::Type("content"),
- CastInfo::Type("function"),
- ])
- }
-}
diff --git a/library/src/basics/mod.rs b/library/src/basics/mod.rs
index 5916df6b..431cb004 100644
--- a/library/src/basics/mod.rs
+++ b/library/src/basics/mod.rs
@@ -1,9 +1,14 @@
//! Common document elements.
+mod desc;
+#[path = "enum.rs"]
+mod enum_;
mod heading;
mod list;
mod table;
+pub use self::desc::*;
+pub use self::enum_::*;
pub use self::heading::*;
pub use self::list::*;
pub use self::table::*;