How to detect the number of items in a list?

I’m trying to create some numerated lists, and use each of them in separated levels. For instance, I have a function similar to the following:

// allows a counter to span over the whole document
#let c = counter("level1")

#let list_level1(body) = {
  set enum(numbering: it => {
    c.step()
    context{
      let num = c.get().first()
      let lvl1_prefix = text("Lvl1. ", weight: "bold")
      let lvl1_counter = numbering("1", num)
      if num < 10{
        text(lvl1_prefix+lvl1_counter+"º")
      } else{
        text(lvl1_prefix+lvl1_counter)
      }
    }
  }) 
  body
}

In my main file I use:

#import "file.typ": list_level1

#list_level1[
  + Item 1
  + Item 2
  ...
  + Item 10
]

This give me the following output:

Lvl1. 1º Item 1
Lvl1. 2º Item 2

Lvl1. 10 Item 10

Now I want to create another enum function to be called in the second level. I already could create it, however I’m struggling to find how many items exist in this level. To be clearer, I want to achieve the following:

Lvl1. 1º Item 1
  Single. Subitem 1

It shows the Single. if I have only one subitem, otherwise it should show:

Lvl1. 1º Item 1
  Lvl2. Subitem 1
  Lvl2. Subitem 2
  …

A few moments ago I was trying the following:

#let list_level2(body) = {
  let itens = body.children
  set enum(numbering: it => {
    let par_prefix = text("Lvl2. ", weight: "bold")
    let par_counter = numbering("1", it)

     if itens.len() == 1{
       text("Single. ")
     } else{
       if it < 10{
         text(par_prefix + par_counter+"º")
       } else{
         text(par_prefix+par_counter)
       }
     }
  }, indent: -2em)
  body
}

Then, in the main file:

#import "file.typ": list_level1, list_level2

#list_level1[
  + Item 1
    #list_level2[
      + Subitem 1.1
      + Subitem 2.1
    ]
  + Item 2
    #list_level2[
      + Subitem 2.1
    ]
  ...
  + Item 10
]

However I could never get the Single. prefix, even with only one subitem for Item 2. In my research through the forum, docs and AI, I could find the query and selector, but I was unable to use them with enum.

Furthermore I found the items.len() returns the value 3 despite declaring only subitem 2.1. When I hover the mouse over item it shows:

Sampled values
([ ], item(body: [Subitem 2.1]), [ ])

This array explains the value 3, but I could not find a way to change this. In the main file I tried:

#list_level1[Item 1][Item 2] ...

and

#list_level1(
  [Item 1]
  [Item 2]
  ...
)

and

#list_level1 {
  [Item 1]
  [Item 2]
  ...
}

None worked. Could anyone help me on this?

yep as you can see you’re getting some stuff that’s not enum items in your sequence (the [ ]s). all you need to do is filter out the stuff that isn’t enum items. like this:

if items.filter(i => i.func() == enum.item).len() == 1 {
    [*Single. *]

that should do it!

1 Like

however, here’s another way to do it. it’s a bit simpler and lets you use plain enums without wrappers:

#let enum-level = state("enum-level", 0)
#show enum.item: it => {
  enum-level.update(l => l + 1)
  it
  enum-level.update(l => l - 1)
}

#let single-item = state("single-item", false)
#show enum: it => {
  single-item.update(it.children.filter(c => c.func() == enum.item).len() == 1)
  it
  single-item.update(false)
}

#set enum(numbering: n => context {
  if single-item.get() [*Single. *]
  else [*Lvl#enum-level.get().* #n;º]
})

+ hi hello
+ what's up
  + nun much
    + yea
  + oh ok
    + uhm
    + so
+ how bout you
  + yea

3 Likes

Thank you very much! It works perfectly. I appreciated the simpler version as well. However, I really need some wrappers due to some complexity my enum environment shall have.

For instance, level 1 must have a global counter whilst the other levels must have local counters. Furthermore, sometimes I will need to jump from level 1 directly to level 3, jumping over level 2. Surely there must be a way to do this ending up with a single enum function. However, all the research, tests and stresses from spending hours and hours trying to make some specific behavior work, may not be worthy. Separated and simpler functions obey the “divide to conquer” principle in here, I guess.

Unless there is a simple and direct way to do what I described.

Unless there is a simple and direct way to do what I described.

don’t tempt me!!! here it is:

#let enum-level = state("enum-level", 0)
#let level-skip = state("level-skip", false)
#let is-item(thing) = thing.func() == enum.item
#show enum.item: it => {
  enum-level.update(l => l + 1)
  level-skip.update(is-item(it.body)
    or it.body.has("children") and it.body.children.all(c => is-item(c) or c == [ ]))
    
  it
  enum-level.update(l => l - 1)
  level-skip.update(false)
}

#let single-item = state("single-item", false)
#show enum: it => {
  single-item.update(it.children.filter(c => c.func() == enum.item).len() == 1)
  it
  single-item.update(false)
}


#let enum-counter = counter("enum")
#enum-counter.step()
#set enum(numbering: num => context {
  let num = if enum-level.get() == 1 {
    enum-counter.step() + enum-counter.display()
  } else { num }
  
  if level-skip.get() []
  else if single-item.get() [*Single. *]
  else [*Lvl#enum-level.get().* #num;º]
})

you skip over a level by adding two or more + markers in one line. single-item level skips aren’t obvious due to the Single marker (see “yup”). the result:

+ hi hello
+ what's up
  + nun much
    + yea
  + oh ok
    + uhm
      + + yup
          + wow
          + wtf
    + so
+ how bout you
  + + yea
    + okay
  + hello

some text here

+ continued!
  + feels good

1 Like