Notes on TeXmacs logo[main]Notes on TeXmacs

Behind the markup: internal macros of TeXmacs

A guided tour of the internal macros and primitives that TeXmacs uses behind the scenes: the render-* hooks of the standard styles, footnotes, labels and the binding mechanism, loci and links, and the processing of title data. Each topic starts from how TeXmacs works and ends with examples you can try.

[Version 1.0 of 24.10.2026. Based on the forum wiki thread Undocumented internal macros in TeXmacs and its contributors.]

This article grew out of a wiki thread on the TeXmacs forum, started in 2020 by re4zuaFe and extended over the years by jeroen, jingkaimori and sharkc, with suggestions by pireddag and mgubi. The thread collected tags that appear in documents and style files but are not (or only barely) described in the manual. Here we reorganize that material, check it against the source code and put it into the wider context of how TeXmacs typesets a document, so that the individual tags stop looking like magic.

All statements below refer to TeXmacs 2.1.4 and to the development version (SVN trunk, revision 15370, October 2026); the relevant files are identical in the two versions. Links to the sources point to the GitHub mirror of the official repository. All the examples were tested with TeXmacs 2.1.4.

Examples of TeXmacs markup are shown as source code: tag names are between angle brackets, and names in italics are macro arguments (like body) or environment variables (like font-size). The easiest way to enter them is in source mode (Document→Source→Edit source tree).

1.How TeXmacs turns markup into pages

To understand internal macros we first need a picture of what a TeXmacs document is and of what happens when it is displayed.

Documents are trees.
A TeXmacs document is a labelled tree. When you write a sentence with a footnote, the editor stores something like

Some text<footnote|A note.> and more text.

which, seen from Scheme (for instance with (tm->stree (buffer-tree)) in a Scheme session), is the tree

(document
  (concat "Some text" (footnote "A note.") " and more text."))

Every node has a label (concat, footnote, …) and children. You can always see this structure in source mode (Document→Source→Edit source tree).

Primitives and macros.
Some labels are primitives: they are known to the C++ typesetter, which knows how to turn them into boxes. They are listed in src/Kernel/Types/tree_label.hpp, and their arity and properties are declared in src/Data/Drd/drd_std.cpp. Examples are concat, with, macro, locus or set-binding. All other labels are macros, defined in style files and packages (the .ts files under TeXmacs/packages) with the assign primitive:

<assign|footnote-sep|<macro|. >>

When the typesetter meets a tag which is not a primitive, it looks up its name in the environment, finds a macro and typesets the body of the macro with the arguments substituted. So footnote is not built into TeXmacs: it is an ordinary macro of the env-float package, and you can read its definition.

The environment.
The environment is a map from names to values which contains both typesetting variables (like font-size or par-left) and macros. assign changes a value from that point onwards, while with changes it locally:

<with|font-shape|italic|footnote-sep|<macro|: >|body>

Since macros live in the same environment as variables, a style package, a document preamble or even a with can redefine a macro of the standard styles. This is the basic customization mechanism of TeXmacs, and it is the reason why internal macros are worth knowing: they are the places where the standard styles let you plug in your own behaviour.

Some primitives are macros in disguise.
A few tags that are documented as primitives, like label, reference, pageref, hlink and action, are in fact macros: they have tree labels in tree_label.hpp, but the typesetter expands them from the environment, where their default definitions are hard-coded in C++, in src/Typeset/Env/env_default.cpp. They are typeset like any other macro and can therefore be redefined from a document or a style file. We will see their definitions in Sections 4 and 5; they are written in terms of the truly primitive tags discussed in this article.

How to look under the hood yourself.
The tools for exploring macros are all available from the editor:

2.The render-* hooks

The standard styles follow a consistent pattern: a user-level macro takes care of the logic (numbering, labels, table of contents entries, …) and then delegates the visual layout to a separate macro, whose name usually starts with render-. These rendering hooks are internal: you normally do not insert them in a document (an exception is render-code, which the Pseudo code menu entry inserts), but redefining them changes the look of every instance of the corresponding environment, without touching the numbering or the references.

The most useful ones are:

Hook Used by Defined in
render-footnote, render-footnote* footnote env-float.ts
render-big-figure, render-small-figure, render-caption big-figure, small-figure, big-table, … env-float.ts
render-theorem theorem, lemma, proposition, … env-enunciation.ts
render-enunciation render-theorem, render-remark, … env-enunciation.ts
render-remark, render-proof, render-exercise remark, proof, exercise, … env-enunciation.ts
render-item, render-list item, itemize, enumerate, … std-list.ts
render-abstract abstract title-base.ts
render-code cpp-code, scm-code, … env-program.ts
render-table-of-contents, render-index table-of-contents, the-index, … section-base.ts

Table 1. Some rendering hooks of the standard styles.

Not every hook follows the naming scheme. Short pieces of text have their own small macros, for instance theorem-name and theorem-sep (the style of the name of a theorem and the separator after it), enunciation-name and enunciation-sep (the same for all theorem-like environments), proof-text (the word “Proof”), abstract-text (the word “Abstract”), figure-name and figure-sep, footnote-sep, and item-vsep and item-hsep (the spacing of lists). The titles of documents use doc-author and doc-authors (Section 6).

The figure and theorem hooks, render-list, the section hooks render-table-of-contents and render-index, and footnote-sep are documented in the section of the manual on customizing the standard styles; the footnote, code, abstract and item hooks (render-item, item-vsep, item-hsep) are not.

Example: theorems with a dash.
Put the following in the preamble of a document (Document→Part→Create preamble, or Show preamble if it already exists), or in a style package of your own:

<assign|theorem-sep|<macro| – >>

<assign|render-theorem|<macro|which|body|<render-enunciation|<with|color|dark blue|<theorem-name|which<theorem-sep>>>|body>>>

<assign|proof-text|<macro|Demonstration>>

Theorems now read “Theorem 1 – Every even number …”, and since lemma, proposition and the other theorem-like environments are all created with new-theorem, which uses render-theorem, they all change at once. Proofs start with “Demonstration.”. Note that our version of render-theorem no longer sets the body in italics: the original definition wraps body in <with|font-shape|italic|…>, and it is a good habit to look at the original (with Edit macro) before replacing it.

Example: compact lists, renamed abstract.
Smaller hooks are just as easy:

<assign|item-vsep|<macro|0fn>>

<assign|abstract-text|<macro|Summary>>

<assign|enunciation-name|<macro|name|<em|name>>>

removes the vertical space between list items and around lists, renames the abstract and sets the names of remarks, examples, theorems… in italics.

3.Footnotes from the inside

The first two entries of the forum thread were render-footnote* and render-footnote. Footnotes are a good example of the whole machinery at work: a counter, a hook, a floating object, a label, a binding and a reference. Here are the definitions from TeXmacs/packages/environment/env-float.ts, slightly simplified (we removed some purely presentational with and style-with tags):

<assign|footnote|<macro|body|<next-footnote><render-footnote|<the-footnote>|body><space|0spc><label|<merge|footnr-|<the-footnote>>><rsup|<reference|<merge|footnote-|<the-footnote>>>>>>

<assign|render-footnote|<macro|nr|body|<render-footnote*|nr|nr|body>>>

<assign|render-footnote*|<macro|sym|nr|body|<float|footnote||<smaller|<surround|<locus|<id|<hard-id|body>>|<link|hyperlink|<id|<hard-id|body>>|<url|<merge|#footnr-|nr>>>|sym><footnote-sep>|<set-binding|<merge|footnote-|nr>|the-label|body><right-flush>|body>>>>>

Let us read them from the top.

  1. footnote first calls next-footnote, which increments the footnote counter. Like every counter created with new-counter, it also stores the new number in the environment variable the-label (see Section 4).

  2. It then calls the hook render-footnote with the displayed number the-footnote and the text of the note.

  3. Back in the main text it places a label called footnr-1 (for the first footnote) and, as a superscript, a reference to footnote-1. The number you see in the text is therefore a reference, which is why it is clickable.

  4. render-footnote simply calls render-footnote* with the number used twice, once as the symbol to display (sym) and once as the number used in the names of the labels (nr).

  5. render-footnote* creates a float of type footnote, which the page breaker moves to the bottom of the page. Inside, the symbol is wrapped into a locus with a hyperlink to #footnr-1: clicking the number in the footnote takes you back to the text. Then comes footnote-sep (by default a period and a space) and finally a set-binding which defines footnote-1 as the current value of the-label.

This is more precise than the description in the forum thread (post 1): footnote-N is not created by a label but by a set-binding, and the label footnr-N is placed by footnote, not by render-footnote*. The third argument of set-binding, here the macro argument name body, tells the typesetter where the binding should point: references and page references to footnote-1 lead to the beginning of the text of the footnote.

The split between render-footnote and render-footnote* presumably exists so that you can display a symbol different from the number, for instance an asterisk or a dagger, while keeping numbered labels.

Example: footnotes with bracketed numbers.

<assign|footnote-sep|<macro|: >>

<assign|render-footnote|<macro|nr|body|<render-footnote*|<with|font-series|bold|[nr]>|nr|body>>>

With these definitions the footnotes read “[1]: Customized footnote body.” Since the labels still exist, you can refer to a note and to the place where it was called:

See note <reference|footnote-1> on page <pageref|footnote-1>; it is called on page <pageref|footnr-1>.

A remark about HTML.
The HTML exporter keeps only the body of a locus and ignores set-binding. In the exported page the footnote mark points to #footnote-1, but the footnote itself gets no such anchor and there is no link back. The scripts which generate this website add the missing anchor after the export.

4.Labels, references and bindings

Footnotes have shown that references are based on a more general mechanism: bindings. A binding associates a key with a value (and with the page on which it was defined). It is created by the primitive set-binding, listed in the forum thread, and read by get-binding:

<set-binding|key|value>

Binds key to value. It produces no visible output. An optional third argument names a macro argument whose position is used as the target of the binding.

<set-binding|value>

With a single argument, binds every key collected in the variable the-tags (by enclosing tag macros) to value, clears the-tags, and sets the-label to value.

<get-binding|key>

Returns the value bound to key; <get-binding|key|1> returns the page number instead.

<has-binding|key>

Returns true or false.

Now we can read the default definitions of label and reference in src/Typeset/Env/env_default.cpp. In TeXmacs notation they are, up to some details,

<assign|label|<macro|Id|<locus|<id|<hard-id|Id>>|<link|anchor|<id|<merge|#|Id>>>|<set-binding|Id|the-label>>>>

<assign|reference|<macro|Id|<locus|<id|<hard-id|Id>>|<link|hyperlink|<id|<hard-id|Id>>|<url|<merge|#|Id>>>|<get-binding|Id>>>>

A label binds its name to the current value of the-label, and a reference displays the value bound to the name inside a link to it. Where does the-label come from? From the counters. The new-counter macro of TeXmacs/packages/standard/std-counter.ts defines, for a counter X, a macro next-X which increments the counter and then executes <set-binding|<the-X>>, the one-argument form which sets the-label. This is why a label placed after a numbered section, equation or footnote picks up the right number.

Example: playing with bindings.
Try the following in a document. The easiest way to enter it is in source mode; in normal mode, typing \set-binding and Return inserts the one-argument form, still inactive; a second argument is added with Focus→Insert argument after, and Return activates the tag.

<set-binding|my-key|Hello>Value: <reference|my-key>, <get-binding|my-key>. Defined: <has-binding|my-key>, <has-binding|other>.

<set-binding|Alpha><label|lab-b>See <reference|lab-b>.

<tag|tag-d|<set-binding|Delta>>See <reference|tag-d>.

The output is “Value: Hello, Hello. Defined: true, false.”, then “See Alpha.” and “See Delta.” The second line shows that a label can be given any text as its value, not only a number. The third uses tag, which attaches a label to the next one-argument set-binding inside its body.

Bindings need several passes.
A reference can come before the corresponding label, so its value is not known during the first typesetting. TeXmacs stores the bindings of a document (you can see them in the <references|…> section at the end of a saved .tm file) and uses the values of the previous pass. Document→Update→All regenerates the auxiliary data and typesets the document again (once by default; the number of passes can be changed in the preferences); repeat it if some references are still wrong. Undefined references are displayed as “?”. From Scheme, you can inspect the current bindings with (list-references) and (get-reference "my-key"), which returns the value together with the page number.

Redefining references.
Since reference is a macro, a document can redefine it. The following puts every reference between brackets (including the footnote marks, which are references too):

<assign|reference|<macro|Id|<locus|<id|<hard-id|Id>>|<link|hyperlink|<id|<hard-id|Id>>|<url|<merge|#|Id>>>|[<get-binding|Id>]>>>

The preview-ref package (in TeXmacs/packages/utilities/preview-ref.ts) uses exactly this technique to show a preview of the target when the mouse hovers over a reference.

5.Loci, links and the linking tool

Most of the remaining tags of the forum thread (locus, id, url, link, observer, script and hard-id) belong to the linking primitives, a general system for attaching identities to parts of documents and for connecting them with typed links. Hyperlinks, references, footnote back-links, actions and “mirrors” are all built on top of it. This part of TeXmacs is almost entirely undocumented, so it deserves a closer look.

5.1.The tags

<locus|item-1|…|item-n|body>

A locus is a piece of the document, body, which can be the source or the target of links. The arguments before the body are id, link or observer tags. When the locus is typeset, its identifiers are registered in a global link repository and the body is displayed in the locus-color (or visited-color once it has been followed). Clicking on a locus follows its links.

<id|identifier>

Gives an identifier to the surrounding locus. Several loci may share the same identifier (for instance after a copy and paste); they then behave as a single entity.

<link|type|vertex-1|…|vertex-n>

A link of a given type (hyperlink, action, anchor, mouse-over, or any name you like) between several vertices. A vertex is an id (another locus), an url or a script.

<url|location>

A vertex pointing to a document, a web page or an anchor (#name) inside a document.

<script|function|arg-1|…>

A vertex which runs a Scheme function when the link is followed. Following such links is subject to the security settings of TeXmacs. (Do not confuse this tag with the scripts package (in packages/compute/scripts.ts) or with mathematical sub- and superscripts.)

<observer|identifier|callback>

Attaches a Scheme function to the locus, which is called when the content of the locus changes. The callback is only registered if it is declared secure (with (:secure #t) in its tm-define) or if the document is in a secure location (a directory listed in $TEXMACS_SECURE_PATH).

<hard-id|content>

Returns an identifier for content, valid during the current editing session. The argument is optional.

As noted in the forum thread for id, url and link (and as we checked, also for script and observer), these tags have no effect outside a locus: on their own they are just displayed as inactive markup.

About hard-id the thread (post 1) says that it generates a hash from the given text. This is not quite right. Its implementation (exec_hard_id in src/Typeset/Env/env_exec.cpp) returns a string built from the memory addresses of the typesetting environment and of its argument, like %134FBF300-127B80; a hash of the content is added only for arguments which do not come from the document. Two <hard-id|abc> in the same document therefore yield different values, and the values change from one session to the next. On the other hand, the same argument tree always gives the same value, which is why hlink below can write <hard-id|body> twice and obtain matching identifiers. The values are never saved: hard-id is meant to give an anonymous identity to an object that only needs to be found again during the current session, typically the locus of a hyperlink.

5.2.Everyday links are loci

The definition of hlink in src/Typeset/Env/env_default.cpp is

<assign|hlink|<macro|body|destination|<locus|<id|<hard-id|body>>|<link|hyperlink|<id|<hard-id|body>>|<url|destination>>|body>>>

This is the first structure described by jingkaimori (post 7): a locus with an anonymous identity and one link of type hyperlink from that identity to the destination URL. Similarly, action creates a link of type action to a script, and we have already seen that label, reference and footnotes use loci too.

You can write such a locus by hand. In the following two lines, the first locus has a link to an external page, the second has a link to a third locus somewhere else in the document:

<locus|<id|+web1>|<link|hyperlink|<id|+web1>|<url|https://www.texmacs.org>>|TeXmacs site>

<locus|<id|+srcA>|<link|hyperlink|<id|+srcA>|<id|+dstB>>|go to target> … <locus|<id|+dstB>|the target>

Clicking on “go to target” moves the cursor to “the target”. The converse does not happen, because TeXmacs by default does not follow links backwards (see Link→Navigation options). Hand-made loci work in the editor. In PDF output, loci whose link points to an url (including #label) become clickable, while links between two loci only work in the editor; the HTML exporter keeps only the body of a locus. For web pages, use hlink.

5.3.The linking tool

Loci can also be created interactively. Enable Tools→Linking tool; a Link menu appears. The procedure found by jeroen (post 8) is:

  1. Insert a locus with Link→New locus and type some text in it.

  2. With the cursor inside it, choose Link→Source.

  3. Insert a second locus elsewhere and choose Link→Target→Locus inside it. (Link→Target→Url and Link→Target→Script set other kinds of targets.)

  4. Create the link with Link→Create link→hyperlink, or choose Other to give the link a type of your own.

The Link→Link mode submenu decides where the link is stored. In the default Bidirectional mode, the same link is inserted in both loci, so that the link is known whichever of the two loci is loaded; Simple stores it only in the source, and External only in the locus which contains the cursor when the link is created (possibly in a third document), so that links between files can be kept separately. Other entries of the menu are Delete link, Locus rendering, Active link types (which link types are followed on a click) and Extract, whose entries Loci and Constellation list the loci of the document and the documents known to the link repository.

Two warnings. First, a user reported several crashes while experimenting with the linking tool, so save your work before trying it. Second, while writing this article we found that Link→Delete link in bidirectional mode removes the link only from the current locus and not from its partner: inside TeXmacs/progs/link/link-edit.scm a local definition of link-vertices shadows the global one used to find the other end of the link. The same shadowing breaks link-source and link-target, so that in Simple mode the Delete link entry fails altogether (and possibly the whole Link menu, which uses the same function to decide whether to show it). Moreover, the test which selects the links of the current locus in that mode compares an identifier with a vertex <id|…>, so it would fail even with the shadowing fixed.

5.4.Loci from Scheme

jeroen also explored the Scheme interface to loci (post 6). The functions are defined in TeXmacs/progs/link/locus-edit.scm, TeXmacs/progs/link/link-edit.scm and TeXmacs/progs/link/link-navigate.scm, and in the C++ glue. They are available globally. The functions which only read or change loci by identifier can be tried in a Scheme session (in the same window as the document, or with the document as the current buffer). With the two loci +srcA and +dstB of the example above in the current document:

(id->trees "+dstB")          ; => the bodies of the loci with id +dstB
(id->loci "+dstB")           ; => the locus trees themselves
(locus-id (car (id->loci "+srcA")))       ; => "+srcA"
(current-link-types)         ; => ("hyperlink") : link types in use
(map tm->stree (vertex->links '(id "+srcA")))
;; => ((link "hyperlink" (attr "secure" "false") (id "+srcA") (id "+dstB")))
(locus-set "+dstB" '(strong "replaced"))  ; change every +dstB locus

A few things to keep in mind:

The menu actions themselves can be scripted, but since they act at the cursor, they must run with the cursor in the document, not in a Scheme session: put them in a function and bind it to a key (see the companion article Customizing TeXmacs with Scheme). The following creates two loci at the cursor and a hyperlink between them, exactly as the steps of the previous subsection:

(make-locus)          ; like Link -> New locus
(insert "first")
(link-set-locus 0)    ; like Link -> Source
;; … move the cursor elsewhere …
(make-locus)
(insert "second")
(link-set-locus 1)    ; like Link -> Target -> Locus
(make-link "hyperlink")

5.5.Observers and mirrors

The second structure described by jingkaimori (post 7) uses an observer. It is what the mirror tag of the relate package expands to (see TeXmacs/packages/utilities/relate.ts):

<locus|<id|channel>|<observer|unique-id|mirror-notify>|<surround|<hidden|<extern|mirror-initialize|…>>||body>>

Note that the identifier of the locus (a “channel”) and the identifier of the observer (unique to each instance) are different. All loci sharing the same channel identifier are kept synchronized: whenever the body of one of them changes, the Scheme callback mirror-notify (in TeXmacs/progs/part/part-shared.scm) copies the new content into the others. To try it, add the relate package to a document with Document→Style→Add package; a Relate menu with a Mirror entry appears. Insert a mirror (you are asked for the name of a channel), insert a second mirror with the same channel name, or copy and paste the first one, and edit one of the copies.

6.Title data: doc-author and doc-authors

The last entry of the thread concerns doc-authors, a macro which is easily confused with doc-author. As sharkc explained (post 10), it is not meant to be typed by the user. The title block of a document is a doc-data tag, whose children are the title, the authors, the date and so on:

<doc-data|<doc-title|Two authors>|<doc-author|<author-data|<author-name|Ada Lovelace>|<author-email|ada@example.org>>>|<doc-author|<author-data|<author-name|Alan Turing>|<author-affiliation|Bletchley Park>>>>

This is what you get by inserting a title and then adding a second author from the focus toolbar while the cursor is in the title. The macro doc-data, defined in TeXmacs/packages/header/title-base.ts, does not typeset its arguments directly. It is an xmacro which passes them, with extern, to the Scheme function doc-data of TeXmacs/progs/database/title-markup.scm. That function applies the options of the title (abbreviated authors, clustering by affiliation, …) and reorganizes the data; in particular it groups two or more doc-author children into a single doc-authors:

(tm-define (doc-data-main t)
  `(document
     ,@(select t '(doc-title))
     ,@(select t '(doc-subtitle))
     ,@(with authors (select t '(doc-author))
         (if (<= (length authors) 1) authors
             (list `(doc-authors ,@authors))))
     ,@(select t '(doc-date))
     ,@(select t '(doc-misc))
     ,@(select t '(doc-inactive))))

The resulting tree is then typeset. doc-authors is therefore a rendering hook in the sense of Section 2: its role is to arrange several authors side by side, and a style can redefine it to change that layout. To give a document several authors, simply add several doc-author entries; writing doc-authors yourself inside doc-data makes the authors disappear, because the Scheme code only looks for doc-author children. (The manual calls these hooks render-doc-authors and render-doc-author in its section on customizing titles; the first does not exist, and the second only in the book and seminar title styles. The macros used by the standard styles are doc-authors and doc-author.)

The same pattern, a macro which delegates the structure to Scheme with extern and lets the result be typeset by ordinary macros, is used elsewhere in TeXmacs. It explains why some tags cannot be inserted manually with the expected effect: they are the output of a Scheme transformation, not its input.

7.Summary

The forum thread remains open for further discoveries; corrections and additions to this article are welcome as pull requests to the repository of this website.