Skip to content

Configuration Compose Modules

reminiscience edited this page Aug 13, 2026 · 1 revision

Compose modules

Which compose sequences are available is decided by a list in config.json. The sequence files themselves live in compose/, in XCompose format, and can be edited or added to without touching the program.

"composeModules": [
    "base", "cyrillic", "diacritics", "en_US",
    "greek", "lang", "math", "ananeo-hochtief", "ananeo-kreis", "ananeo-schrift"
],
"compose": {
    "oskAutoShow": false
}

composeModules

A positive list: only what is named here is loaded. Write the file name without its extension. The order is the load order, and that matters – see collisions below.

What ships

Module Sequences Contents
base 305 the common core – accents, punctuation, currency
cyrillic 173 Cyrillic beyond Russian: Ukrainian, Serbian, Turkic letters
diacritics 33 combining marks
en_US 4191 the large inherited body: pictorial sequences, symbols, everything
greek 728 polytonic Greek – breathings, accents, iota subscript
lang 389 language-specific letters
math 625 mathematical symbols and relations
ananeo-hochtief 107 superscript and subscript, complete: 70 raised, 37 lowered
ananeo-kreis 256 enclosed characters – circled, parenthesised, squared, negative
ananeo-schrift 1021 the Mathematical Alphanumeric Symbols block, all six families

Two more files exist but are not in the default list: klingon and klingon-kp. Together they account for about two thirds of the loading time, which is why they are off unless you ask for them.

The last three are AnaNeo's own. Two of them – ananeo-kreis and ananeo-schrift – are generated from code, not maintained by hand, and a test holds file and calculation together. Do not edit them; see Compose.

When sequences collide

Two modules may define the same sequence, and the load order decides:

  • An exact duplicate overwrites the earlier result. The later module wins.
  • A sequence that extends an existing one, or that ends part-way inside one, is discarded. It could never be reached, since the shorter sequence would already have fired.

A debug build writes a report about all of this to the log once loading finishes. The ananeo-tool compose origin command shows the same thing without a debug build, and names the individual sequences that lost – see Compose tool.

.remove files

Each module may have a .remove file of the same name, which deletes entries again. The rules are worth knowing because they are not symmetrical:

  • A .remove file is read if its module is in your list, or if there is no module of that name at all. The second case covers built-in special routines – unicode.remove belongs to Unicode input, which is code rather than a file.
  • All .remove files are read before the first module loads. Their effect therefore does not depend on the module line they were aimed at still existing.

That last point has a consequence: if you delete a line from a module and a .remove file of the same module named that sequence, you do not get the old behaviour back – you get an unassigned sequence.

A missing module name costs a whole branch

A name in the list with no matching file loads nothing, and nothing else fails: the program starts, every other module loads, and a branch of the compose tree is simply absent. Since a release build keeps no log, this used to be invisible.

It is now reported: AnaNeo shows a message box at startup naming any module it could not find.

After an update

composeModules is an array, and arrays are replaced whole in the merge – so newly shipped modules do not appear in an existing config.json by themselves.

Two mechanisms soften this:

  • Renamed modules are carried over. hochtief became ananeo-hochtief; a config still naming the old one is corrected on start. This is safe precisely because the old name points at a file that no longer exists, so it cannot be a deliberate choice.
  • Newly shipped modules are added, at the right position in the list, guided by configVersion. If you switch a module off after that migration has run, your choice stands – the marker records that the step has been seen.

If you are coming from an older version and something is missing, deleting the "composeModules" entry from config.json once and restarting always gets you the current default list.

compose

One option so far:

  • oskAutoShow (false by default) – open the on-screen keyboard when a compose sequence starts and close it when the sequence ends, unless it was already open. With it on, the keyboard shows which keys continue the sequence and which finish it, which is the fastest way to explore a branch. The matching option for locks is locks.oskAutoShow.

Writing your own module

The format is XCompose:

<Multi_key> <a> <e>       : "æ"   U00E6
<U02E3> <1>               : "¹"   U00B9

Left of the colon is the key sequence as keysyms, right of it the resulting string and its keysym. Put the file in compose/, add its name to composeModules, restart.

Two rules apply beyond the format itself:

  • A compose branch needs a key. Every root character of the tree must exist somewhere on a layer of AnNoted – otherwise the branch cannot be started at all, and a test will fail. This applies to the root only; a keysym in the middle of a sequence need not be typable.
  • The layer carries the grip, the tree carries the derivation. A character may sit on a layer and in the tree, as long as the sequence is part of a system. A sequence that merely duplicates a single key belongs nowhere. See Compose.

A module can also declare sticky nodes, which keep a modifier open like a mode instead of applying once. That is described under Compose.

Clone this wiki locally