Document escaping semicolon, valid identifiers, and state tips (#6674)

Co-authored-by: Andrew Voynov <37143421+Andrew15-5@users.noreply.github.com>
Co-authored-by: Yaksher <yaksher.git@gmail.com>
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Y.D.X. 2025-08-07 23:02:43 +08:00 committed by GitHub
parent 3455ac7dd2
commit 0b639c7510
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 95 additions and 9 deletions

View File

@ -75,7 +75,10 @@ impl Label {
/// Creates a label from a string.
#[func(constructor)]
pub fn construct(
/// The name of the label. Must not be empty.
/// The name of the label.
///
/// Unlike the [dedicated syntax](#syntax), this constructor accepts
/// any non-empty string, including names with special characters.
name: Str,
) -> StrResult<Label> {
if name.is_empty() {

View File

@ -407,7 +407,7 @@ impl Counter {
/// Create a new counter identified by a key.
#[func(constructor)]
pub fn construct(
/// The key that identifies this counter.
/// The key that identifies this counter globally.
///
/// - If it is a string, creates a custom counter that is only affected
/// by manual updates,

View File

@ -273,8 +273,31 @@ impl State {
#[func(constructor)]
pub fn construct(
/// The key that identifies this state.
///
/// Any [updates]($state.update) to the state will be identified with
/// the string key. If you construct multiple states with the same
/// `key`, then updating any one will affect all of them.
key: Str,
/// The initial value of the state.
///
/// If you construct multiple states with the same `key` but different
/// `init` values, they will each use their own initial value but share
/// updates. Specifically, the value of a state at some location in the
/// document will be computed from that state's initial value and all
/// preceding updates for the state's key.
///
/// ```example
/// #let banana = state("key", "🍌")
/// #let broccoli = state("key", "🥦")
///
/// #banana.update(it => it + "😋")
///
/// #context [
/// - #state("key", "🍎").get()
/// - #banana.get()
/// - #broccoli.get()
/// ]
/// ```
#[default]
init: Value,
) -> State {
@ -328,7 +351,7 @@ impl State {
Ok(sequence.last().unwrap().clone())
}
/// Update the value of the state.
/// Updates the value of the state.
///
/// The update will be in effect at the position where the returned content
/// is inserted into the document. If you don't put the output into the
@ -336,13 +359,44 @@ impl State {
/// write `{let _ = state("key").update(7)}`. State updates are always
/// applied in layout order and in that case, Typst wouldn't know when to
/// update the state.
///
/// In contrast to [`get`]($state.get), [`at`]($state.at), and
/// [`final`]($state.final), this function does not require [context].
#[func]
pub fn update(
self,
span: Span,
/// If given a non function-value, sets the state to that value. If
/// given a function, that function receives the previous state and has
/// to return the new state.
/// A value to update to or a function to update with.
///
/// - If given a non-function value, sets the state to that value.
/// - If given a function, that function receives the state's previous
/// value and has to return the state's new value.
///
/// When updating the state based on its previous value, you should
/// prefer the function form instead of retrieving the previous value
/// from the [context]($context). This allows the compiler to resolve
/// the final state efficiently, minimizing the number of
/// [layout iterations]($context/#compiler-iterations) required.
///
/// In the following example, `{fill.update(f => not f)}` will paint odd
/// [items in the bullet list]($list.item) as expected. However, if it's
/// replaced with `{context fill.update(not fill.get())}`, then layout
/// will not converge within 5 attempts, as each update will take one
/// additional iteration to propagate.
///
/// ```example
/// #let fill = state("fill", false)
///
/// #show list.item: it => {
/// fill.update(f => not f)
/// context {
/// set text(fill: fuchsia) if fill.get()
/// it
/// }
/// }
///
/// #lorem(5).split().map(list.item).join()
/// ```
update: StateUpdate,
) -> Content {
StateUpdateElem::new(self.key, update).pack().spanned(span)

View File

@ -14,7 +14,7 @@ provides compact syntax to embed a code expression into markup: An expression is
introduced with a hash (`#`) and normal markup parsing resumes after the
expression is finished. If a character would continue the expression but should
be interpreted as text, the expression can forcibly be ended with a semicolon
(`;`).
(`;`). You can [escape a literal `#` or `;` with a backslash]($syntax/#escapes).
```example
#emph[Hello] \
@ -66,6 +66,7 @@ Content and code blocks can be nested arbitrarily. In the example below,
## Bindings and Destructuring { #bindings }
As already demonstrated above, variables can be defined with `{let}` bindings.
The variable is assigned the value of the expression that follows the `=` sign.
A [valid variable name](#identifiers) may contain `-`, but cannot start with `-`.
The assignment of a value is optional, if no value is assigned, the variable
will be initialized as `{none}`. The `{let}` keyword can also be used to create
a [custom named function]($function/#defining-functions). Variables can be
@ -77,8 +78,8 @@ is no containing block).
This is #name's documentation.
It explains #name.
#let add(x, y) = x + y
Sum is #add(2, 3).
#let my-add(x, y) = x + y
Sum is #my-add(2, 3).
```
Let bindings can also be used to destructure [arrays]($array) and

View File

@ -168,6 +168,34 @@ I got an ice cream for
\$1.50! \u{1f600}
```
## Identifiers
Names of variables, functions, and so on (_identifiers_) can contain letters,
numbers, hyphens (`-`), and underscores (`_`). They must start with a letter or
an underscore.
More specifically, the identifier syntax in Typst is based on the
[Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/), with two
extensions: Allowing `_` as a starting character, and allowing both `_` and `-`
as continuing characters.
For multi-word identifiers, the recommended case convention is
[Kebab case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case). In Kebab
case, words are written in lowercase and separated by hyphens (as in
`top-edge`). This is especially relevant when developing modules and packages
for others to use, as it keeps things predictable.
```example
#let kebab-case = [Using hyphen]
#let _schön = "😊"
#let 始料不及 = "😱"
#let π = calc.pi
#kebab-case
#if< 0 { _schön } else { 始料不及 }
// -π means -1 * π,
// so it's not a valid identifier
```
## Paths
Typst has various features that require a file path to reference external
resources such as images, Typst files, or data files. Paths are represented as