Emacs mode for Klammertext
klammertext-mode.el is an Emacs major mode for editing Klammertext files. It
helps Klammertext authors see the structure of klammer application through
syntax highlighting.
Install
Put the emacs directory somewhere on your system, then tell Emacs where it is
and load the mode. Add to ~/.emacs.d/init.el:
(add-to-list 'load-path "full-pathname-of-the-emacs-directory")
(require 'klammertext-mode)
(require 'klammertext-indent) ; optional, experimental: TAB indentation
(require 'klammertext-align) ; optional, experimental: table alignment
Replace full-pathname-of-the-emacs-directory with the full path to the
directory that contains klammertext-mode.el. The last two requires load
the experimental indentation and table-alignment support (see their sections
below); each is a separate unit — comment its line out to disable it
entirely.
The mode auto-activates for .kt and .k files. (The .k / .kt distinction
is a filing convention, not a lexical one — the same mode serves both.) You can
also switch to it manually with M-x klammertext-mode.
What it highlights
Text-removal ("ignore") constructs — in two independently chosen colors, one for the removed content, one for the marker characters:
| Construct | Meaning |
|---|---|
# ... |
remove to end of line |
## ... |
remove to end of buffer |
#[ ... ]# |
remove enclosed text (nestable) |
Klammer applications — in two independent colors: one for opening a
klammer, one for closing it. The @ and the name of an opening are one
syntactic unit and share the opening color; the close (named or bare) gets the
closing color, so you always have visual confirmation of where a klammer ends:
| Form | Color | Meaning |
|---|---|---|
@name |
opening | opening @ + name (one unit) |
name@ |
closing | named closing delimiter |
@ |
closing | bare closing delimiter |
In the abbreviated form @name-arg1-arg2 (equivalent to @name arg1 | arg2 @)
only the name is colored — the name ends at the first hyphen, and the
hyphen-separated arguments stay plain, just as arg1/arg2 would be plain in
the long form.
Because a named closing carries the closing color across its name too, long
klammers that name their closing delimiter (@document ... document@) stand
out — which is exactly where naming the closing delimiter earns its keep
(accurate unmatched-delimiter error messages). Short bodies (@i word @) are
conventionally left with a bare @ to keep the text uncluttered.
Klammer definitions (@@) are highlighted the same way, in their own pair
of colors — so definitions read as distinct from applications at a glance:
| Form | Color | Meaning |
|---|---|---|
@@name |
opening | opening @@ + name (one unit) |
name@@ |
closing | named closing delimiter |
@@ |
closing | bare closing delimiter |
The name ends at the first non-name character, so a target suffix like
@@name.html colors only @@name and leaves .html plain. A definition's
body (between @@name and the closing @@) is highlighted like ordinary
Klammertext — e.g. an @i … @ inside it shows as a normal application.
System/target commands (@@@) — @@@target, @@@argtype, @@@state — get
a third pair of colors, so the three @-levels (application, definition, system)
are visually distinct:
| Form | Color | Meaning |
|---|---|---|
@@@name |
opening | opening @@@ + name (one unit) |
name@@@ |
closing | named closing delimiter |
@@@ |
closing | bare closing delimiter |
@@@ commands do not nest, so each delimiter is colored independently; their
bodies (| … | option lists) are highlighted as ordinary Klammertext.
The eight faces
All eight faces are defined by the defconst klammertext--palette at the
beginning of klammertext-mode.el.
| Face | Applies to |
|---|---|
klammertext-ignored-face |
removed content |
klammertext-marker-face |
#, ##, #[, ]# |
klammertext-klammer-open-face |
an application opening @name |
klammertext-klammer-close-face |
an application close name@ or @ |
klammertext-def-open-face |
a definition opening @@name |
klammertext-def-close-face |
a definition close name@@ or @@ |
klammertext-system-open-face |
a system opening @@@name |
klammertext-system-close-face |
a system close name@@@ or @@@ |
To experiment with a color (evaluate in *scratch*, or add to your init):
(set-face-foreground 'klammertext-marker-face "cyan")
(set-face-foreground 'klammertext-system-close-face "chocolate4")
or M-x customize-face RET klammertext-marker-face RET.
Matching delimiters (show-paren)
With show-paren-mode on (the default in Emacs 28+), placing point on a klammer
application delimiter highlights its partner, in both directions: on an
opening @name it highlights the closing @/name@, and on a close it
highlights the opening @name. Nesting is respected — in @a @b x @ @, the
outer @a matches the last @, not the first.
Matching covers applications only (@), not @@/@@@, since that is where
paired delimiters matter most. The matcher steps over @@/@@@, removed text,
other literal spans and escaped ^@; the abbreviated @name-arg form has no
closing delimiter, so nothing is highlighted on it.
Literal klammers (those in klammertext-literal-klammers, e.g. @code) are
closed with the full NAME@ form because their content is verbatim. These are
matched by name — @code ↔ code@ — with the content treated as opaque, so a
stray @ inside (@code x @ y code@) doesn't confuse the match, in either
direction. Register any klammer that declares a literal argument with
(klammertext-add-literal-klammer "name") in your init file so both its
highlighting and its delimiter matching work.
If a named close disagrees with its opening — e.g. @doc … foo@ (should be
doc@) — the mismatched delimiter is shown in bright red (bold), and a
message describing the mismatch appears in the minibuffer, e.g.
Klammertext: closing foo@ does not match opening @doc
An unbalanced delimiter (an opening with no close, or vice versa) is flagged the
same way. This turns the naming convention into a live check: name a long
klammer's closing delimiter and a typo'd or unbalanced name lights up
immediately. The red comes from klammertext-mismatch-face, which is remapped
over show-paren-mismatch only in Klammertext buffers (your global
show-paren-mismatch face is left untouched); customize it to taste.
This is wired in automatically (show-paren-data-function); you only need
show-paren-mode enabled. It relies on nothing in the syntax table — Klammertext
delimiters can't be expressed there — so it does not interfere with other
@/# characters.
Jumping between matches
klammertext-jump-to-match, bound to C-c C-j, moves point to the matching
delimiter: from an opening @name to its close, and from a close back to its
opening @name. It uses the same matcher as the highlighting. The starting
position is pushed to the mark ring, so C-u C-SPC jumps back. (Also available
as M-x klammertext-jump-to-match.)
Indentation (experimental)
With klammertext-indent.el loaded (the optional require above), TAB
indents the current line — and indent-region a selection — to reflect the
klammer nesting, two spaces per level:
@ol
Item one
| Item two
@ol
Embedded item one
| Embedded item two
@
| Item three
@
The rule: a line indents to 2 × depth; a line beginning with a bar run
(|, ||, …) or a closing delimiter sits one level less — at its owner's
opening column. So the bars and the close line up under the @ of the list
they belong to, and | (bar + space) puts item text exactly at the content
column. The bar rule is dimension-independent: || table rows drop to the
opener's column the same way. All three @-tiers indent uniformly.
Exceptions, all deliberate:
- Klammers in
klammertext-transparent-klammers(default:document) contribute no level, so a document's ordinary paragraphs stay at the left margin. - Lines inside a literal klammer's verbatim content (
@code ... code@), and inside the argument span of a klammer inklammertext-code-klammers(default:eval— inline Python is indentation-sensitive), are never touched. Neither are removed regions (#[ ... ]#, after##).
Nothing reformats automatically — whitespace is content in Klammertext, so
indentation happens only when you ask for it (TAB, indent-region). The
offset is klammertext-indent-offset (default 2); all three variables are
customizable in the klammertext-indent group.
Table alignment (experimental)
With klammertext-align.el loaded (the optional require above), C-c C-a
with point anywhere inside a @table span pads the cells of its rows so the
| separators line up:
@table
First item | Second | A third item that's longer ||
Row 2 | Text | Not as long ||
@
A row is one line ending with the row delimiter || (the customary trailing
delimiter — the parser strips one trailing delimiter, and it keeps every row
uniform, which also suits program-generated tables); the last row may omit
it. Alignment is for small data items, so a row is left untouched — and
contributes nothing to the column widths — when any of its cells is longer
than klammertext-align-cell-max (30) characters or the row spans lines.
If the aligned rows would exceed klammertext-align-row-max (100) columns,
nothing changes and the reason is reported.
The padding is semantically free: the SKS strips cell content, and no
whitespace is ever inserted inside a bar run (which would turn a || row
separator into an empty | | cell). Bars inside a nested klammer in a cell
(@frac 1 | 2 @) belong to that klammer, not the table, and are left alone.
Aligned rows adopt the leading whitespace of the first aligned row — run TAB
first if the rows disagree.
Literal klammers
Inside a literal argument — for example the body of @code ... code@ — #
and @ are literal text, not Klammertext syntax. The mode highlights the
opening @code and closing code@ but leaves the interior as normal text,
for any klammer registered in klammertext-literal-klammers. @code is
registered by default.
If you define your own klammer with a literal parameter (a relatively
advanced action — see the literal argument type in the project
documentation), register it in your init file:
(klammertext-add-literal-klammer "myverbatim")
Because the list is consulted at fontification time, registering a klammer
while a buffer is already open takes effect after M-x font-lock-update (or
re-visiting the file).
Known limitations (deliberate, for now)
- Unescaped
@is always treated as a delimiter (as the Klammermachine does), so an@in prose that is not meant as a klammer — e.g. an email address writtenfoo@barinstead offoo^@bar— will be highlighted. This reflects what the machine actually sees. - Very large multiline blocks or literal spans edited far from their opening
may occasionally need
M-x font-lock-updateto re-highlight correctly.