summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-09-26 15:39:32 +0200
committerLaurenz <laurmaedje@gmail.com>2022-09-26 16:12:57 +0200
commit704f2fbaf1b4483caa12f249a222c49e44f08961 (patch)
tree146f7813fe63409df2c1bbaa487731e992d3ac71 /src/library
parent2661f1a5066bd5e3f8a9c68e4a5c304c248efcb7 (diff)
Description lists, link syntax, and new enum syntax
Diffstat (limited to 'src/library')
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/structure/list.rs233
-rw-r--r--src/library/text/link.rs7
-rw-r--r--src/library/text/par.rs4
-rw-r--r--src/library/text/raw.rs2
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