Syntax overriding

(This is not a question so much as a discussion of what the idiomatic solution would be in future Typst versions, I assume at the very least that we would have custom elements and replace rules)

Suppose you want to add some extra content to each level 1 heading, content that isn’t semantically part of the heading itself, like a table of contents for that chapter.

We can already do this with a show rule but the extra content will be considered part of the heading which is bad.

In the future though, you could use a replace rule or (ignoring syntax convenience) use a custom element where the show rule outputs the real heading as well as the extra content.

By Replace Rule

If we only got replace rules and custom elements, then the first option would be clearly preferred: The source element is still a regular heading, so you can still use the usual syntax sugar for headings.

However this might lead to problems with the replace rule matching it’s own output.

There are a lot of ways this might not be a problem, but they have issues:
  • If replace rules don’t match on the original element if it is included in the output
  • Smuggle some data in one of the fields to allow the selectors to distinguish between elements created by syntax sugar and the ones that are the result of one of your show/replace rules.

But the first of these is unreliable in more complex scenarios and the second is just messy and may be impossible for some elements.

By Custom Element

If the issue of syntax is ignored though, then using a custom element is (in my opinion at least) the cleaner solution because there’s no issue of the show rule matching on it’s output.

Mini-conclusion

To solve the issues of the replace rules solution would require having a better way to distinguish between elements constructed with syntax sugar and other elements

To solve the issues of the custom elements solution would require a way to change what element the syntax sugar outputs.

Support for general user-defined syntax sugar would also solve the problem but I can’t see that actually happening.

A viable “way to distinguish between elements constructed with syntax sugar and other elements” might just be to have each element with syntax sugar have a completely different element (called the syntax sugar version of the element) that the syntax sugar actually outputs. This is then replaced with the actual e.g. heading by a builtin replace rule.

Edit: This also makes a lot of sense since the syntax sugar versions consistently don’t let you set all the fields that the raw element supports, which could be neatly reflected by the syntax sugar version of the element lacking those fields.
As for naming, I suggest adding a new module to std, std.sugar for accessing the syntax sugar versions of elements. This is better than e.g. heading.sugar because it avoids cluttering up the documentation for elements that have a syntax sugar version


We could also add an extra hidden field to elements with syntax sugar which can be retrieved using a builtin function but this just feels too magic and too messy.