diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-09-26 15:39:32 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-09-26 16:12:57 +0200 |
| commit | 704f2fbaf1b4483caa12f249a222c49e44f08961 (patch) | |
| tree | 146f7813fe63409df2c1bbaa487731e992d3ac71 /src/library | |
| parent | 2661f1a5066bd5e3f8a9c68e4a5c304c248efcb7 (diff) | |
Description lists, link syntax, and new enum syntax
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/mod.rs | 1 | ||||
| -rw-r--r-- | src/library/structure/list.rs | 233 | ||||
| -rw-r--r-- | src/library/text/link.rs | 7 | ||||
| -rw-r--r-- | src/library/text/par.rs | 4 | ||||
| -rw-r--r-- | src/library/text/raw.rs | 2 |
5 files changed, 177 insertions, 70 deletions
diff --git a/src/library/mod.rs b/src/library/mod.rs index 91e4671c..e7617bc0 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -41,6 +41,7 @@ pub fn new() -> Scope { std.def_node::<structure::HeadingNode>("heading"); std.def_node::<structure::ListNode>("list"); std.def_node::<structure::EnumNode>("enum"); + std.def_node::<structure::DescNode>("desc"); std.def_node::<structure::TableNode>("table"); // Layout. diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 9da14733..7a43e5db 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -1,5 +1,3 @@ -use std::fmt::Write; - use unscanny::Scanner; use crate::library::layout::{BlockSpacing, GridNode, TrackSizing}; @@ -9,9 +7,7 @@ use crate::library::utility::Numbering; /// An unordered (bulleted) or ordered (numbered) list. #[derive(Debug, Hash)] -pub struct ListNode<const L: ListKind = UNORDERED> { - /// Where the list starts. - pub start: usize, +pub struct ListNode<const L: ListKind = LIST> { /// If true, the items are separated by leading instead of list spacing. pub tight: bool, /// If true, the spacing above the list is leading instead of above spacing. @@ -20,19 +16,11 @@ pub struct ListNode<const L: ListKind = UNORDERED> { pub items: StyleVec<ListItem>, } -/// An item in a list. -#[derive(Clone, PartialEq, Hash)] -pub struct ListItem { - /// The kind of item. - pub kind: ListKind, - /// The number of the item. - pub number: Option<usize>, - /// The node that produces the item's body. - pub body: Box<Content>, -} - /// An ordered list. -pub type EnumNode = ListNode<ORDERED>; +pub type EnumNode = ListNode<ENUM>; + +/// A description list. +pub type DescNode = ListNode<DESC>; #[node(showable)] impl<const L: ListKind> ListNode<L> { @@ -44,7 +32,11 @@ impl<const L: ListKind> ListNode<L> { pub const INDENT: RawLength = RawLength::zero(); /// The space between the label and the body of each item. #[property(resolve)] - pub const BODY_INDENT: RawLength = Em::new(0.5).into(); + pub const BODY_INDENT: RawLength = Em::new(match L { + LIST | ENUM => 0.5, + DESC | _ => 1.0, + }) + .into(); /// The spacing above the list. #[property(resolve, shorthand(around))] @@ -57,19 +49,34 @@ impl<const L: ListKind> ListNode<L> { pub const SPACING: BlockSpacing = Ratio::one().into(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show(Self { - start: args.named("start")?.unwrap_or(1), - tight: args.named("tight")?.unwrap_or(true), - attached: args.named("attached")?.unwrap_or(false), - items: args + let items = match L { + LIST => args .all()? .into_iter() - .map(|body| ListItem { - kind: L, - number: None, - body: Box::new(body), - }) + .map(|body| ListItem::List(Box::new(body))) .collect(), + ENUM => { + let mut number: usize = args.named("start")?.unwrap_or(1); + args.all()? + .into_iter() + .map(|body| { + let item = ListItem::Enum(Some(number), Box::new(body)); + number += 1; + item + }) + .collect() + } + DESC | _ => args + .all()? + .into_iter() + .map(|item| ListItem::Desc(Box::new(item))) + .collect(), + }; + + Ok(Content::show(Self { + tight: args.named("tight")?.unwrap_or(true), + attached: args.named("attached")?.unwrap_or(false), + items, })) } } @@ -77,10 +84,7 @@ impl<const L: ListKind> ListNode<L> { impl<const L: ListKind> Show for ListNode<L> { fn unguard(&self, sel: Selector) -> ShowNode { Self { - items: self.items.map(|item| ListItem { - body: Box::new(item.body.unguard(sel).role(Role::ListItemBody)), - ..*item - }), + items: self.items.map(|item| item.unguard(sel)), ..*self } .pack() @@ -88,13 +92,12 @@ impl<const L: ListKind> Show for ListNode<L> { fn encode(&self, _: StyleChain) -> Dict { dict! { - "start" => Value::Int(self.start as i64), "tight" => Value::Bool(self.tight), "attached" => Value::Bool(self.attached), "items" => Value::Array( self.items .items() - .map(|item| Value::Content(item.body.as_ref().clone())) + .map(|item| item.encode()) .collect() ), } @@ -106,34 +109,54 @@ impl<const L: ListKind> Show for ListNode<L> { styles: StyleChain, ) -> SourceResult<Content> { let mut cells = vec![]; - let mut number = self.start; + let mut number = 1; let label = styles.get(Self::LABEL); + let indent = styles.get(Self::INDENT); + let body_indent = styles.get(Self::BODY_INDENT); + let gutter = if self.tight { + styles.get(ParNode::LEADING) + } else { + styles.get(Self::SPACING) + }; for (item, map) in self.items.iter() { - number = item.number.unwrap_or(number); + if let &ListItem::Enum(Some(n), _) = item { + number = n; + } cells.push(LayoutNode::default()); - cells.push( + + let label = if L == LIST || L == ENUM { label .resolve(world, L, number)? .styled_with_map(map.clone()) .role(Role::ListLabel) - .pack(), - ); + .pack() + } else { + LayoutNode::default() + }; + + cells.push(label); cells.push(LayoutNode::default()); - cells.push((*item.body).clone().styled_with_map(map.clone()).pack()); - number += 1; - } - let gutter = if self.tight { - styles.get(ParNode::LEADING) - } else { - styles.get(Self::SPACING) - }; + let body = match &item { + ListItem::List(body) => body.as_ref().clone(), + ListItem::Enum(_, body) => body.as_ref().clone(), + ListItem::Desc(item) => Content::sequence(vec![ + Content::Horizontal { + amount: (-body_indent).into(), + weak: false, + }, + (item.term.clone() + Content::Text(':'.into())).strong(), + Content::Space, + item.body.clone(), + ]), + }; - let indent = styles.get(Self::INDENT); - let body_indent = styles.get(Self::BODY_INDENT); + cells.push(body.styled_with_map(map.clone()).pack()); + number += 1; + } Ok(Content::block(GridNode { tracks: Spec::with_x(vec![ @@ -165,35 +188,110 @@ impl<const L: ListKind> Show for ListNode<L> { } } - Ok(realized - .role(Role::List { ordered: L == ORDERED }) - .spaced(above, below)) + Ok(realized.role(Role::List { ordered: L == ENUM }).spaced(above, below)) + } +} + +/// An item in a list. +#[derive(Clone, PartialEq, Hash)] +pub enum ListItem { + /// An item of an unordered list. + List(Box<Content>), + /// An item of an ordered list. + Enum(Option<usize>, 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, + } + } + + fn unguard(&self, sel: Selector) -> Self { + match self { + Self::List(body) => Self::List(Box::new(body.unguard(sel))), + Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))), + Self::Desc(item) => Self::Desc(Box::new(DescItem { + term: item.term.unguard(sel), + body: item.body.unguard(sel), + })), + } + } + + /// 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 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()), + }), + } } } impl Debug for ListItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.kind == UNORDERED { - f.write_char('-')?; - } else { - if let Some(number) = self.number { - write!(f, "{}", number)?; - } - f.write_char('.')?; + match self { + Self::List(body) => write!(f, "- {body:?}"), + Self::Enum(number, body) => match number { + Some(n) => write!(f, "{n}. {body:?}"), + None => write!(f, "+ {body:?}"), + }, + Self::Desc(item) => item.fmt(f), } - f.write_char(' ')?; - self.body.fmt(f) + } +} + +/// A description list item. +#[derive(Clone, PartialEq, Hash)] +pub struct DescItem { + /// The term described by the list item. + pub term: Content, + /// The description of the term. + pub body: Content, +} + +castable! { + DescItem, + Expected: "dictionary with `term` and `body` keys", + Value::Dict(dict) => { + let term: Content = dict.get("term")?.clone().cast()?; + let body: Content = dict.get("body")?.clone().cast()?; + Self { term, body } + }, +} + +impl Debug for DescItem { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "/ {:?}: {:?}", self.term, self.body) } } /// How to label a list. pub type ListKind = usize; -/// Unordered list labelling style. -pub const UNORDERED: ListKind = 0; +/// An unordered list. +pub const LIST: ListKind = 0; + +/// An ordered list. +pub const ENUM: ListKind = 1; -/// Ordered list labelling style. -pub const ORDERED: ListKind = 1; +/// A description list. +pub const DESC: ListKind = 2; /// How to label a list or enumeration. #[derive(Debug, Clone, PartialEq, Hash)] @@ -218,8 +316,9 @@ impl Label { ) -> SourceResult<Content> { Ok(match self { Self::Default => match kind { - UNORDERED => Content::Text('•'.into()), - ORDERED | _ => Content::Text(format_eco!("{}.", number)), + LIST => Content::Text('•'.into()), + ENUM => Content::Text(format_eco!("{}.", number)), + DESC | _ => panic!("description lists don't have a label"), }, Self::Pattern(prefix, numbering, upper, suffix) => { let fmt = numbering.apply(number); diff --git a/src/library/text/link.rs b/src/library/text/link.rs index c06fea55..78ae4d23 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -10,6 +10,13 @@ pub struct LinkNode { pub body: Option<Content>, } +impl LinkNode { + /// Create a link node from a URL with its bare text. + pub fn from_url(url: EcoString) -> Self { + Self { dest: Destination::Url(url), body: None } + } +} + #[node(showable)] impl LinkNode { /// The fill color of text in the link. Just the surrounding text color diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 6910c23a..859a7c87 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -181,8 +181,8 @@ pub struct LinebreakNode; #[node] impl LinebreakNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let justified = args.named("justified")?.unwrap_or(false); - Ok(Content::Linebreak { justified }) + let justify = args.named("justify")?.unwrap_or(false); + Ok(Content::Linebreak { justify }) } } diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index e7c73a91..a64b1a92 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -91,7 +91,7 @@ impl Show for RawNode { let mut highlighter = HighlightLines::new(syntax, &THEME); for (i, line) in self.text.lines().enumerate() { if i != 0 { - seq.push(Content::Linebreak { justified: false }); + seq.push(Content::Linebreak { justify: false }); } for (style, piece) in |
