How to store the value of a counter in a state

Hi,
like mentioned in the title, I want to store the value of a counter in a state. The ultimate goal is to achieve something like the exercism package does (but it does not correspond exactly and I don’t really understand the code).
I want that code

#q("one","a")
#q("two","b")
#q("three","c")
#printstate()

give me the result

1 one
2 two
3 three
1 a
2 b
3 c

I tried to do something like

#let mycounter = counter("mc")
#let mystate = state("state", ())
#let q(a,b) = [
  #mycounter.step()
  #mystate.update(ans=>{ans.push([#context mycounter.display() #b]);ans})
  #context mycounter.display() #a \
]

#let printstate() = context {
  for a in mystate.get() [
    #a \
  ]
}

#q("one","a")
#q("two","b")
#q("three","c")

#printstate()

but, obviously, it’s not working… I get

1 one
2 two
3 three
3 a
3 b
3 c

Any help ?

Remark : I need to store the value of the counter (it can be multilevel, like 1.2.1)

Hi!
You need to get the counter value at the specific location, where you update your state.

I solved this by using a label.

I’m not shure if this is the best way, but it’s working.


#let mycounter = counter("mc")
#let mystate = state("state", ())
#let q(a,b) = [
  #mycounter.step()
  #label("pos-"+str(a))
  #mystate.update(ans=>{ans.push((mycounter,a,b));ans})
  #context mycounter.display() #a \
]

#let printstate() = context {
  for a in mystate.get() [
    #context a.at(0).at(label("pos-"+str(a.at(1)))).at(0) #a.at(2)\
  ]
}

#q("one","a")
#q("two","b")
#q("three","c")

#printstate()

This results in the desired output.

Thank’s @Mathemensch
It works with the example I gave, but in my real code both parameters of #q could be content, not just string. Then it’s not working. My bad.
For example

#q(lorem(120),"a")
#q(lorem(250),"b")
#q([three $1 +2$],"c")

works for line 1 and 2 (I was testing if there was a limit in the definition of labels) but not for line 3

I think the issue here is that you store [#context mycounter.display()] in the state, which is then evaluated wherever the state is rendered – any snippet of content with a context inside of it gets that context rendered when it is placed in the final document. The solution here is to store the displayed counter itself:

#let q(a,b) = {
  mycounter.step()
  context {
    let number = mycounter.display() // render with context of q function call
    mystate.update(ans => {
      ans.push([#number #b])         // store rendered number
      ans
    })
    [#number #a \ ]
  }
}

I.e. we store [1 a], instead of [#context /*...*/ a].

(See also the examples in Context – Typst Documentation)

3 Likes

I think the issue here is that you store [#context mycounter.display()] in the state, which is then evaluated wherever the state is rendered

I did understand the problem was not using the right context.

I will play around with your code tomorrow, but it sure looks like the solution to my problem (in this case I will tag my post as solved).

Thank’s a lot @nleanba

1 Like