Configurable markers for nested lists
This commit is contained in:
parent
05c8c6045c
commit
28b5c55cd5
@ -150,7 +150,7 @@ impl EnumNode {
|
||||
|
||||
/// The spacing between the items of a wide (non-tight) enumeration.
|
||||
///
|
||||
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
||||
|
||||
/// The numbers of parent items.
|
||||
|
@ -76,16 +76,29 @@ pub struct ListNode {
|
||||
|
||||
#[node]
|
||||
impl ListNode {
|
||||
/// The marker which introduces each element.
|
||||
/// The marker which introduces each item.
|
||||
///
|
||||
/// Instead of plain content, you can also pass an array with multiple
|
||||
/// markers that should be used for nested lists. If the list nesting depth
|
||||
/// exceeds the number of markers, the last one is repeated. For total
|
||||
/// control, you may pass a function that maps the list's nesting depth
|
||||
/// (starting from `{0}`) to a desired marker.
|
||||
///
|
||||
/// Default: `•`
|
||||
///
|
||||
/// ```example
|
||||
/// #set list(marker: [--])
|
||||
///
|
||||
/// - A more classic list
|
||||
/// - With en-dashes
|
||||
///
|
||||
/// #set list(marker: ([•], [--]))
|
||||
/// - Top-level
|
||||
/// - Nested
|
||||
/// - Items
|
||||
/// - Items
|
||||
/// ```
|
||||
#[property(referenced)]
|
||||
pub const MARKER: Content = TextNode::packed('•');
|
||||
pub const MARKER: Marker = Marker::Content(vec![]);
|
||||
|
||||
/// The indent of each item's marker.
|
||||
#[property(resolve)]
|
||||
@ -97,9 +110,13 @@ impl ListNode {
|
||||
|
||||
/// The spacing between the items of a wide (non-tight) list.
|
||||
///
|
||||
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
||||
|
||||
/// The nesting depth.
|
||||
#[property(skip, fold)]
|
||||
const DEPTH: Depth = 0;
|
||||
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Self {
|
||||
tight: args.named("tight")?.unwrap_or(true),
|
||||
@ -126,7 +143,6 @@ impl Layout for ListNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
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 {
|
||||
@ -137,12 +153,17 @@ impl Layout for ListNode {
|
||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
||||
};
|
||||
|
||||
let depth = styles.get(Self::DEPTH);
|
||||
let marker = styles.get(Self::MARKER).resolve(vt.world(), depth)?;
|
||||
|
||||
let mut cells = vec![];
|
||||
for (item, map) in self.items.iter() {
|
||||
cells.push(Content::empty());
|
||||
cells.push(marker.clone());
|
||||
cells.push(Content::empty());
|
||||
cells.push(item.clone().styled_with_map(map.clone()));
|
||||
cells.push(
|
||||
item.clone().styled_with_map(map.clone()).styled(Self::DEPTH, Depth),
|
||||
);
|
||||
}
|
||||
|
||||
GridNode {
|
||||
@ -158,3 +179,51 @@ impl Layout for ListNode {
|
||||
.layout(vt, styles, regions)
|
||||
}
|
||||
}
|
||||
|
||||
/// A list's marker.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Marker {
|
||||
Content(Vec<Content>),
|
||||
Func(Func),
|
||||
}
|
||||
|
||||
impl Marker {
|
||||
/// Resolve the marker for the given depth.
|
||||
fn resolve(&self, world: Tracked<dyn World>, depth: usize) -> SourceResult<Content> {
|
||||
Ok(match self {
|
||||
Self::Content(list) => list
|
||||
.get(depth)
|
||||
.or(list.last())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| TextNode::packed('•')),
|
||||
Self::Func(func) => {
|
||||
let args = Args::new(func.span(), [Value::Int(depth as i64)]);
|
||||
func.call_detached(world, args)?.display()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
castable! {
|
||||
Marker,
|
||||
v: Content => Self::Content(vec![v]),
|
||||
array: Array => {
|
||||
if array.len() == 0 {
|
||||
Err("must contain at least one marker")?;
|
||||
}
|
||||
Self::Content(array.into_iter().map(Value::display).collect())
|
||||
},
|
||||
v: Func => Self::Func(v),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
struct Depth;
|
||||
|
||||
impl Fold for Depth {
|
||||
type Output = usize;
|
||||
|
||||
fn fold(self, mut outer: Self::Output) -> Self::Output {
|
||||
outer += 1;
|
||||
outer
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ impl TermsNode {
|
||||
|
||||
/// The spacing between the items of a wide (non-tight) term list.
|
||||
///
|
||||
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
||||
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
@ -171,7 +171,7 @@ castable! {
|
||||
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")?,
|
||||
_ => Err("term array must contain exactly two entries")?,
|
||||
};
|
||||
Self { term, description }
|
||||
},
|
||||
|
BIN
tests/ref/layout/list-marker.png
Normal file
BIN
tests/ref/layout/list-marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
34
tests/typ/layout/list-marker.typ
Normal file
34
tests/typ/layout/list-marker.typ
Normal file
@ -0,0 +1,34 @@
|
||||
// Test list marker configuraiton.
|
||||
|
||||
---
|
||||
// Test en-dash.
|
||||
#set list(marker: [--])
|
||||
- A
|
||||
- B
|
||||
|
||||
---
|
||||
// Test that last item is repeated.
|
||||
#set list(marker: ([--], [•]))
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
|
||||
---
|
||||
// Test function.
|
||||
#set list(marker: n => if n == 1 [--] else [•])
|
||||
- A
|
||||
- B
|
||||
- C
|
||||
- D
|
||||
- E
|
||||
- F
|
||||
|
||||
---
|
||||
// Test that bare hyphen doesn't lead to cycles and crashes.
|
||||
#set list(marker: [-])
|
||||
- Bare hyphen is
|
||||
- a bad marker
|
||||
|
||||
---
|
||||
// Error: 19-21 must contain at least one marker
|
||||
#set list(marker: ())
|
@ -44,11 +44,6 @@ _Shopping list_
|
||||
- A with 2 spaces
|
||||
- B with 2 tabs
|
||||
|
||||
---
|
||||
#set list(marker: [-])
|
||||
- Bare hyphen
|
||||
- is not a list
|
||||
|
||||
---
|
||||
// Edge cases.
|
||||
-
|
||||
|
Loading…
x
Reference in New Issue
Block a user