Skip to content

Commit

Permalink
element counters
Browse files Browse the repository at this point in the history
  • Loading branch information
PgBiel committed Jan 10, 2025
1 parent 6cd11a0 commit 7046e98
Show file tree
Hide file tree
Showing 17 changed files with 229 additions and 9 deletions.
72 changes: 64 additions & 8 deletions src/element.typ
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
// Prefix for the labels added to shown elements.
#let lbl-show-head = "__custom_element_shown_"

// Prefix for counters of elements.
#let lbl-counter-head = "__custom_element_counter_"

// Label for context blocks which have access to the virtual stylechain.
#let lbl-get = <__custom_element_get>

Expand Down Expand Up @@ -238,6 +241,23 @@
(:)
}

// Obtain an element's counter.
//
// USAGE:
//
// #context {
// [The element counter value is #e.counter(elem).get().first()]
// }
#let counter_(elem) = {
let info = data(elem)

if type(info) == dictionary and "data-kind" in info and info.data-kind == "element" {
info.counter
} else {
assert(false, message: "e.counter: this is not an element")
}
}

// Changes stateful mode settings within a certain scope.
// This function will sync all data between all modes (data from normal mode
// goes to state and data from stateful mode goes to normal mode).
Expand Down Expand Up @@ -1234,6 +1254,7 @@
allow-unknown-fields: false,
template: none,
construct: none,
count: counter.step,
synthesize: none,
contextual: auto,
) = {
Expand All @@ -1244,6 +1265,7 @@
assert(type(typecheck) == bool, message: "element.declare: the 'typecheck' argument must be a boolean (true to enable typechecking, false to disable).")
assert(type(allow-unknown-fields) == bool, message: "element.declare: the 'allow-unknown-fields' argument must be a boolean.")
assert(template == none or type(template) == function, message: "element.declare: 'template' must be 'none' or a function displayed element => content (usually set rules applied on the displayed element). This is used to add a set of overridable set rules to the element, such as paragraph settings.")
assert(count == none or type(count) == function, message: "element.declare: 'count' must be 'none', a function counter => counter step/update element, or a function counter => final fields => counter step/update element.")
assert(synthesize == none or type(synthesize) == function, message: "element.declare: 'synthesize' must be 'none' or a function element fields => element fields.")
assert(contextual == auto or type(contextual) == bool, message: "element.declare: 'contextual' must be 'auto' (true if using a contextual feature) or a boolean (true to wrap the output in a 'context { ... }', false to not).")
assert(construct == none or type(construct) == function, message: "element.declare: 'construct' must be 'none' (use default constructor) or a function receiving the original constructor and returning the new constructor.")
Expand All @@ -1255,6 +1277,9 @@

let eid = base.unique-id("e", prefix, name)
let lbl-show = label(lbl-show-head + eid)
let counter = counter(lbl-counter-head + eid)
let count = if count == none { none } else { count(counter) }
let count-needs-fields = type(count) == function

let fields = field-internals.parse-fields(fields, allow-unknown-fields: allow-unknown-fields)
let (all-fields,) = fields
Expand Down Expand Up @@ -1298,6 +1323,7 @@
get: get-rule,
where: where,
sel: lbl-show,
counter: counter,
parse-args: parse-args,
default-data: default-data,
default-global-data: default-global-data,
Expand Down Expand Up @@ -1328,7 +1354,11 @@
}

let args = parse-args(args, include-required: true)
let inner = context {

// Step the counter early if we don't need additional context
let early-step = if not count-needs-fields { count }

let inner = early-step + context {
let previous-bib-title = bibliography.title
[#context {
set bibliography(title: previous-bib-title)
Expand Down Expand Up @@ -1399,26 +1429,51 @@
} else {
synthesize(constructed-fields)
}
let body = display(synthesized-fields)

let tag = tag
tag.fields = synthesized-fields
tag.body = body

[#[#body#metadata(tag)]#lbl-show]
if count-needs-fields {
count(synthesized-fields)

// Wrap in additional context so the counter step is detected
context {
let body = display(synthesized-fields)
let tag = tag
tag.body = body
[#[#body#metadata(tag)]#lbl-show]
}
} else {
let body = display(synthesized-fields)
let tag = tag
tag.body = body
[#[#body#metadata(tag)]#lbl-show]
}
}
} else {
let synthesized-fields = if synthesize == none {
constructed-fields
} else {
synthesize(constructed-fields)
}
let body = display(synthesized-fields)

tag.fields = synthesized-fields
tag.body = body

[#[#body#metadata(tag)]#lbl-show]
if count-needs-fields {
count(synthesized-fields)

// Wrap in additional context so the counter step is detected
context {
let body = display(synthesized-fields)
let tag = tag
tag.body = body
[#[#body#metadata(tag)]#lbl-show]
}
} else {
let body = display(synthesized-fields)
let tag = tag
tag.body = body
[#[#body#metadata(tag)]#lbl-show]
}
}
}

Expand All @@ -1438,6 +1493,7 @@
func: __elembic_func,
default-constructor: default-constructor,
eid: eid,
counter: counter,
fields-known: false,
valid: true
))#lbl-tag]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.typ
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "element.typ": set_, data, apply, revoke, reset, named, style-modes, prepare-get as get, selector, select, fields
#import "element.typ": set_, data, apply, revoke, reset, named, style-modes, prepare-get as get, selector, select, fields, counter_ as counter
#import "fields.typ": field
#import "pub/element.typ"
#import "pub/parsing.typ"
Expand Down
1 change: 1 addition & 0 deletions test/unit/elements/count/func-synthesize/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# added by typst-test
Binary file added test/unit/elements/count/func-synthesize/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions test/unit/elements/count/func-synthesize/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#import "/test/unit/base.typ": empty
#show: empty

#import "/src/lib.typ" as e: field

#let wock = e.element.declare(
"wock",
display: it => {
(it.run)(it)
},
fields: (
field("depth", int, default: 1),
field("offset", int, default: 0),
field("run", function, default: () => {})
),
synthesize: it => {
it.level = it.depth + it.offset
it
},
count: c => it => c.step(level: it.level),
prefix: ""
)

#wock(run: it => {
assert.eq(e.counter(wock).get(), (1,))
})
#wock(run: it => {
assert.eq(e.counter(wock).get(), (2,))
})
#wock(depth: 2, run: it => {
assert.eq(e.counter(wock).get(), (2, 1))
})
#wock(offset: 1, run: it => {
assert.eq(e.counter(wock).get(), (2, 2))
})
#wock(depth: 2, offset: 1, run: it => {
assert.eq(e.counter(wock).get(), (2, 2, 1))
})

#context assert.eq(e.counter(wock).get(), (2, 2, 1))
1 change: 1 addition & 0 deletions test/unit/elements/count/func/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# added by typst-test
Binary file added test/unit/elements/count/func/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions test/unit/elements/count/func/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#import "/test/unit/base.typ": empty
#show: empty

#import "/src/lib.typ" as e: field

#let wock = e.element.declare(
"wock",
display: it => {
(it.run)(it)
},
fields: (
field("level", int, default: 1),
field("run", function, default: () => {})
),
count: c => it => c.step(level: it.level),
prefix: ""
)

#wock(run: it => {
assert.eq(e.counter(wock).get(), (1,))
})
#wock(run: it => {
assert.eq(e.counter(wock).get(), (2,))
})
#wock(level: 2, run: it => {
assert.eq(e.counter(wock).get(), (2, 1))
})
#wock(level: 2, run: it => {
assert.eq(e.counter(wock).get(), (2, 2))
})
#wock(level: 3, run: it => {
assert.eq(e.counter(wock).get(), (2, 2, 1))
})

#context assert.eq(e.counter(wock).get(), (2, 2, 1))
1 change: 1 addition & 0 deletions test/unit/elements/count/none/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# added by typst-test
Binary file added test/unit/elements/count/none/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/unit/elements/count/none/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "/test/unit/base.typ": empty
#show: empty

#import "/src/lib.typ" as e: field

#let wock = e.element.declare(
"wock",
display: it => {
(it.run)(it)
},
fields: (
field("color", color, default: red),
field("inner", content, default: [Hello!]),
field("run", function, default: () => {})
),
count: none,
prefix: ""
)

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 0)
})

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 0)
})

#context assert.eq(e.counter(wock).get().first(), 0)
1 change: 1 addition & 0 deletions test/unit/elements/count/step/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# added by typst-test
Binary file added test/unit/elements/count/step/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/unit/elements/count/step/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "/test/unit/base.typ": empty
#show: empty

#import "/src/lib.typ" as e: field

#let wock = e.element.declare(
"wock",
display: it => {
(it.run)(it)
},
fields: (
field("color", color, default: red),
field("inner", content, default: [Hello!]),
field("run", function, default: () => {})
),
count: counter.step,
prefix: ""
)

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 1)
})

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 2)
})

#context assert.eq(e.counter(wock).get().first(), 2)
1 change: 1 addition & 0 deletions test/unit/elements/count/update/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# added by typst-test
Binary file added test/unit/elements/count/update/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/unit/elements/count/update/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "/test/unit/base.typ": empty
#show: empty

#import "/src/lib.typ" as e: field

#let wock = e.element.declare(
"wock",
display: it => {
(it.run)(it)
},
fields: (
field("color", color, default: red),
field("inner", content, default: [Hello!]),
field("run", function, default: () => {})
),
count: c => c.update(t => t + 2),
prefix: ""
)

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 2)
})

#wock(run: it => {
assert.eq(e.counter(wock).get().first(), 4)
})

#context assert.eq(e.counter(wock).get().first(), 4)

0 comments on commit 7046e98

Please sign in to comment.