[main]Notes on TeXmacs
[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 (
To understand internal macros we first need a picture of what a TeXmacs document is and of what happens when it is displayed.
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
(
<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.
<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.
Put the cursor inside a tag and open the
The source mode (
The style packages themselves are in the packages directory of your TeXmacs installation; searching them for assign|name is often the fastest way to find a definition.
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
The most useful ones are:
|
||||||||||||||||||||||||||||||
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.
<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
<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.
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.
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).
It then calls the hook render-footnote with the displayed number the-footnote and the text of the note.
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.
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).
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.
<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>.
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:
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.
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.
Returns the value bound to key; <get-binding|key|1> returns the page number instead.
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.
<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.
<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.
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.
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.
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.
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.
A vertex pointing to a document, a web page or an anchor (#name) inside a document.
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.)
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).
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.
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
Loci can also be created interactively. Enable
Insert a locus with
Insert a second locus elsewhere and choose
Create the link with
The
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
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:
Loci are registered when they are typeset, so these functions only see loci which have been displayed at least once.
id->loci returns the locus trees, and id->trees their bodies (the contents).
Identifiers are not unique: if you copy a locus, both copies keep
the same identifier, and locus-set changes all of
them. Use (create-unique-id) to obtain fresh
identifiers like +1XmE6jQ4UQg, which is what
(locus-link-types #t) and (locus-link-follow) work on the locus at the cursor, like the menu entries.
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")
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
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.
Tags are either C++ primitives or macros stored in the environment; macros, including some that look like primitives, can be redefined by documents and style packages.
The standard styles separate logic from presentation: the render-* hooks and the small *-name, *-sep and *-text macros are the intended places for customization.
Labels, references, counters and footnotes all rest on bindings, created by set-binding and read by get-binding.
Loci, identified by id and connected by typed links, implement hyperlinks, references, actions and mirrors; the linking tool and a Scheme API let you build your own.
Some macros, like doc-authors, are produced by Scheme code and should not be inserted by hand.
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.