Initial commit: Klammertext source distribution

Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-18 18:48:23 +02:00
commit 2ba7ceee7a
272 changed files with 27634 additions and 0 deletions

183
doc/edit/emacs/README.md Normal file
View File

@@ -0,0 +1,183 @@
# 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`:
```elisp
(add-to-list 'load-path "full-pathname-of-the-emacs-directory")
(require 'klammertext-mode)
```
Replace `full-pathname-of-the-emacs-directory` with the full path to the
directory that contains `klammertext-mode.el`.
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):
```elisp
(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`.)
## 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:
```elisp
(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
written `foo@bar` instead of `foo^@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-update` to re-highlight correctly.

View File

@@ -0,0 +1,680 @@
;;; klammertext-mode.el --- Major mode for Klammertext files -*- lexical-binding: t; -*-
;; An Emacs major mode for editing Klammertext files. It highlights:
;;
;; Text-removal ("ignore") constructs:
;; # ... remove to end of line
;; ## ... remove to end of buffer
;; #[ ... ]# remove enclosed text (nestable)
;;
;; Klammer applications:
;; @name opening delimiter + name
;; name@ named closing delimiter
;; @ bare closing delimiter
;;
;; Klammer definitions (@@), analogous to applications:
;; @@name opening delimiter + name
;; name@@ named closing delimiter
;; @@ bare closing delimiter
;;
;; System/target commands (@@@), analogous again:
;; @@@name opening delimiter + name
;; name@@@ named closing delimiter
;; @@@ bare closing delimiter
;;
;; Independent faces carry each pair of colors: removed content vs. the removal
;; marker characters; and, for applications (@name), definitions (@@name) and
;; system commands (@@@name), each construct's opening vs. its close.
;;
;; The same mode serves both .kt (content) and .k (klammer definition) files:
;; the .k/.kt split is a filing convention, not a lexical difference.
;;
;; Everything is driven by ONE left-to-right scanner (`klammertext--fontify').
;; That is what makes the interactions correct: inside removed text and inside
;; literal-klammer spans the scanner jumps over the content, so it is never
;; re-interpreted as klammers or comments.
;;
;; It also matches klammer APPLICATION delimiters for `show-paren-mode' (both
;; directions, with mismatched-name flagging); see the show-paren section below.
;;
;; Not handled yet (deliberately):
;; * The bodies of definitions (@@) and system commands (@@@) are highlighted
;; like ordinary Klammertext (an @i ... @ inside shows as a normal
;; application), rather than being treated specially.
;; * Inside a `literal' argument (e.g. @code ... code@) neither # nor @ is a
;; marker. The scanner highlights the opening @code and closing code@ but
;; leaves the interior as normal text, for any klammer registered in
;; `klammertext-literal-klammers'.
;;; Code:
(defgroup klammertext nil
"Editing Klammertext files."
:group 'text)
;; --- Faces: colors from a central palette table -------------------------
;;
;; All highlighting colors live in one table, `klammertext--palette', so they
;; can be tuned in a single place. Each face carries a DARK-background value
;; and a LIGHT-background value; Emacs picks automatically from the frame or
;; terminal background (there is no "dark mode" toggle to set).
;;
;; The DARK column is a systematic scheme (developed for the Sublime Text port):
;; three tier hues — application blue, definition green, system orange — each
;; opening bright and its close the same hue at 0.80 intensity.
;;
;; The LIGHT column is a hybrid tuned by eye: the three delimiter opens are the
;; original Emacs colors (RoyalBlue2 / green4 / orange3), each close = 0.60 x its
;; open (from the open name's X11 RGB #436eee/#008b00/#cd8500); the marker and
;; mismatch light values are the systematic ones; the ignored (gray) light value
;; is the systematic gray reduced by 0.90 (#9a9a9a -> #8b8b8b).
(defconst klammertext--palette
;; (face dark light description [extra-attrs])
'((klammertext-ignored-face "#8a8272" "#8b8b8b" "removed (ignored) content")
(klammertext-marker-face "#ff6b6b" "#994040" "removal markers # ## #[ ]#")
(klammertext-klammer-open-face "#89ddff" "RoyalBlue2" "application opening @name")
(klammertext-klammer-close-face "#6eb1cc" "#28428f" "application close name@ or bare @")
(klammertext-def-open-face "#c3e88d" "green4" "definition opening @@name")
(klammertext-def-close-face "#9cba71" "#005300" "definition close name@@ or bare @@")
(klammertext-system-open-face "#ffab70" "orange3" "system opening @@@name")
(klammertext-system-close-face "#cc895a" "#7b5000" "system close name@@@ or bare @@@")
(klammertext-mismatch-face "#ff5555" "#c02020" "mismatched/unbalanced delimiter"
(:weight bold)))
"Klammertext face colors: (FACE DARK LIGHT DESCRIPTION [EXTRA-ATTRS]).
DARK is the foreground on dark backgrounds, LIGHT on light backgrounds; each
face is generated with both. EXTRA-ATTRS, if present, is a plist merged into
both grounds. See the notes above the table.")
;; Generate the faces from the table. Using `custom-declare-face' (what
;; `defface' expands to) keeps each face customizable via M-x customize-face.
(dolist (entry klammertext--palette)
(let ((face (nth 0 entry))
(dark (nth 1 entry))
(light (nth 2 entry))
(desc (nth 3 entry))
(extra (nth 4 entry)))
(custom-declare-face
face
`((((background dark)) :foreground ,dark ,@extra)
(((background light)) :foreground ,light ,@extra))
(format "Klammertext highlighting for %s.\nColor is set from the `klammertext--palette' table." desc)
:group 'klammertext)))
;; --- Klammers whose literal content must not be interpreted -------------
(defcustom klammertext-literal-klammers nil
"Names of klammers whose content is a `literal' argument.
Such a klammer must be closed with the full NAME@ form (e.g. @code ... code@),
because its content is verbatim. This list is consulted in two places:
* Font-lock leaves the verbatim interior as normal text (#, @, etc. inside
are not interpreted).
* Delimiter matching (`show-paren-mode' and `klammertext-jump-to-match')
pairs the opening @NAME with its closing NAME@ *by name* rather than by
depth counting, so a literal close is matched even though its content may
contain unbalanced @ characters.
Any klammer that declares a `literal' argument should be registered here.
Register one with `klammertext-add-literal-klammer', e.g. in your init file:
(klammertext-add-literal-klammer \"mycode\")"
:type '(repeat string)
:group 'klammertext)
;; SYNC: the Sublime Text port in doc/sublime/ duplicates this list statically
;; (a Sublime syntax/plugin cannot read this Emacs defcustom). When you add or
;; remove a literal klammer, mirror it in BOTH:
;; * LITERAL_KLAMMERS in doc/sublime/Klammertext.py
;; * the @NAME literal rule + literal_NAME context in
;; doc/sublime/Klammertext.sublime-syntax
;; All three are currently seeded with just "code".
(defun klammertext-add-literal-klammer (name)
"Register NAME as a klammer whose literal content must not be interpreted.
NAME is the klammer name without the leading @ (e.g. \"code\")."
(add-to-list 'klammertext-literal-klammers name))
;; Seed the list through the same entry point future users will use.
(klammertext-add-literal-klammer "code")
;; --- Helpers -----------------------------------------------------------
(defun klammertext--escaped-p (pos)
"Non-nil if the character at POS is escaped by an odd run of ^ before it.
In Klammertext `^#' and `^@' are literal, so such a character is not a
marker or a delimiter."
(let ((n 0) (i (1- pos)))
(while (and (>= i (point-min)) (eq (char-after i) ?^))
(setq n (1+ n) i (1- i)))
(= (mod n 2) 1)))
(defun klammertext--name-char-p (ch)
"Non-nil if CH can be part of a klammer name (letter, digit or _).
A hyphen is NOT a name character: in the abbreviated form
@name-arg1-arg2 the hyphen separates the name from its arguments, so a
klammer name ends at the first hyphen."
(and ch (or (and (>= ch ?a) (<= ch ?z))
(and (>= ch ?A) (<= ch ?Z))
(and (>= ch ?0) (<= ch ?9))
(eq ch ?_))))
(defun klammertext--block-end (from)
"Return the position just after the ]# that closes a #[ block.
FROM is the position just after the opening #[. Counts nested #[ ... ]#
pairs; returns `point-max' if the block is never closed."
(goto-char from)
(let ((depth 1))
(while (and (> depth 0)
(re-search-forward "#\\[\\|]#" nil t))
(if (string= (match-string 0) "#[")
(setq depth (1+ depth))
(setq depth (1- depth))))
(if (> depth 0) (point-max) (point))))
(defun klammertext--set-match (wb we &rest groups)
"Set match data covering WB..WE with up to nine GROUPS.
Each group is a cons (BEG . END), or nil for an absent group (whose
highlight spec must use LAXMATCH)."
(let ((md (list wb we)))
(dotimes (_ 9)
(let ((g (pop groups)))
(setq md (append md (if g (list (car g) (cdr g)) (list nil nil))))))
(set-match-data md)))
;; --- Token emitters (called by the scanner) ----------------------------
;; Each returns non-nil when it has emitted a highlight token (and set the
;; match data + moved point past it), or nil to let the scanner keep going.
;; Groups: 1 removal-marker 2 removed-content 3 removal-close-marker
;; 4 app-open (@name) 5 app-close (name@ or bare @)
;; 6 def-open (@@name) 7 def-close (name@@ or bare @@)
;; 8 sys-open (@@@name) 9 sys-close (name@@@ or bare @@@)
(defun klammertext--emit-removal (pos _limit)
"POS is at a #. Point is at POS+1 on entry."
(let ((next (char-after (1+ pos))))
(cond
;; ## ... end of buffer
((eq next ?#)
(klammertext--set-match pos (point-max)
(cons pos (+ pos 2))
(cons (+ pos 2) (point-max))
nil nil nil)
(put-text-property pos (point-max) 'font-lock-multiline t)
(goto-char (point-max))
t)
;; #[ ... ]# (nestable)
((eq next ?\[)
(let* ((end (klammertext--block-end (+ pos 2)))
(close (if (and (>= end (+ pos 4))
(eq (char-before end) ?#)
(eq (char-before (1- end)) ?\]))
(- end 2) end)))
(klammertext--set-match pos end
(cons pos (+ pos 2))
(cons (+ pos 2) close)
(cons close end)
nil nil)
(put-text-property pos end 'font-lock-multiline t)
(goto-char end)
t))
;; #+ #/ #- are whitespace operators, NOT removals: keep scanning.
((memq next '(?+ ?/ ?-))
nil)
;; # ... end of line
(t
(let ((eol (line-end-position)))
(klammertext--set-match pos eol
(cons pos (1+ pos))
(cons (1+ pos) eol)
nil nil nil)
(goto-char eol)
t)))))
(defun klammertext--emit-klammer (pos _limit)
"POS is at an @. Point is at POS+1 on entry.
Dispatch by the length of the @-run at POS: a single @ is a klammer
APPLICATION (@name / name@ / @); @@ is a klammer DEFINITION (@@name /
name@@ / @@); @@@ is a system/target command (@@@name / name@@@ / @@@)."
(let ((before (and (> pos (point-min)) (char-before pos))))
(cond
;; Mid-run @ (previous char is @): the run's first @ drives everything,
;; so skip this one (e.g. the @ after an escaped ^@).
((eq before ?@)
(goto-char (1+ pos))
nil)
;; @@@ (or longer): system/target command (@@@target, @@@argtype, ...).
((and (eq (char-after (1+ pos)) ?@)
(eq (char-after (+ pos 2)) ?@))
(klammertext--emit-system pos before))
;; @@ : klammer definition delimiter
((eq (char-after (1+ pos)) ?@)
(klammertext--emit-def pos before))
;; single @ : klammer application
(t
(klammertext--emit-application pos before)))))
(defun klammertext--emit-application (pos before)
"Emit a single-@ klammer-application token at POS (groups 4 open / 5 close).
BEFORE is the character before POS."
(let ((after (char-after (1+ pos))))
(cond
;; @name : opening application (the @ and name are one unit). The name
;; ends at the first hyphen; any -arg1-arg2 abbreviation stays uncolored.
((klammertext--name-char-p after)
(goto-char (1+ pos))
(skip-chars-forward "A-Za-z0-9_")
(let* ((name-end (point))
(name (buffer-substring-no-properties (1+ pos) name-end)))
;; For a literal klammer, first locate the closing NAME@ and move the
;; scanner to its start (skipping the verbatim interior); the closing
;; is highlighted on the next scanner call. The search must happen
;; BEFORE `klammertext--set-match', because `re-search-forward'
;; clobbers the match data.
(if (member name klammertext-literal-klammers)
(let ((close (concat (regexp-quote name) "@")))
(if (re-search-forward close nil t)
(let ((close-end (point)))
(put-text-property pos close-end 'font-lock-multiline t)
(goto-char (- close-end (length name) 1)))
(put-text-property pos (point-max) 'font-lock-multiline t)
(goto-char (point-max))))
(goto-char name-end))
;; Set the match data for the opening LAST, so it survives to the
;; highlight step.
(klammertext--set-match pos name-end
nil nil nil
(cons pos name-end) ; 4: @name opening
nil)
t))
;; A @ preceded by name characters is either a named close NAME@, or the
;; bare close of a compact no-argument application @name@. They differ by
;; what precedes the name run: an @ there means the name belongs to the
;; opening (@name@), so this @ is a bare close and only it is coloured;
;; otherwise the whole NAME@ is the closing token.
((klammertext--name-char-p before)
(let ((name-start (save-excursion
(goto-char pos)
(skip-chars-backward "A-Za-z0-9_")
(point))))
(if (and (> name-start (point-min))
(eq (char-before name-start) ?@))
(klammertext--set-match pos (1+ pos) ; @name@ -> bare @
nil nil nil nil
(cons pos (1+ pos)))
(klammertext--set-match name-start (1+ pos) ; NAME@ named close
nil nil nil nil
(cons name-start (1+ pos))))
(goto-char (1+ pos))
t))
;; bare @ : unnamed closing delimiter
(t
(klammertext--set-match pos (1+ pos)
nil nil nil
nil
(cons pos (1+ pos))) ; 5: bare @ close
(goto-char (1+ pos))
t))))
(defun klammertext--emit-def (pos before)
"Emit a @@ klammer-definition delimiter token at POS (groups 6 open / 7 close).
POS and POS+1 are both @. BEFORE is the character before POS. Mirrors
`klammertext--emit-application', with @@ in place of @."
(let ((after (char-after (+ pos 2)))) ; char right after the @@
(cond
;; @@name : opening definition (the @@ and name are one unit). The name
;; ends at the first non-name char (a space, or the .target suffix).
((klammertext--name-char-p after)
(goto-char (+ pos 2))
(skip-chars-forward "A-Za-z0-9_")
(let ((name-end (point)))
(klammertext--set-match pos name-end
nil nil nil nil nil
(cons pos name-end) ; 6: @@name opening
nil)
(goto-char name-end)
t))
;; name@@ (named close) or the bare close of a compact no-body @@name@@.
;; As with applications, an @ before the name run means the name belongs
;; to the opening, so only the @@ is the closing token.
((klammertext--name-char-p before)
(let ((name-start (save-excursion
(goto-char pos)
(skip-chars-backward "A-Za-z0-9_")
(point))))
(if (and (> name-start (point-min))
(eq (char-before name-start) ?@))
(klammertext--set-match pos (+ pos 2) ; @@name@@ -> bare @@
nil nil nil nil nil nil
(cons pos (+ pos 2)))
(klammertext--set-match name-start (+ pos 2) ; NAME@@ named close
nil nil nil nil nil nil
(cons name-start (+ pos 2))))
(goto-char (+ pos 2))
t))
;; bare @@ : unnamed closing delimiter
(t
(klammertext--set-match pos (+ pos 2)
nil nil nil nil nil nil
(cons pos (+ pos 2))) ; 7: bare @@ close
(goto-char (+ pos 2))
t))))
(defun klammertext--emit-system (pos before)
"Emit a @@@ system/target delimiter token at POS (groups 8 open / 9 close).
POS, POS+1 and POS+2 are all @. BEFORE is the character before POS. These
commands (@@@target, @@@argtype, @@@state) do not nest, so each delimiter is
coloured independently, mirroring `klammertext--emit-def' with @@@ for @@."
(let ((after (char-after (+ pos 3)))) ; char right after the @@@
(cond
;; @@@name : opening command (the @@@ and name are one unit).
((klammertext--name-char-p after)
(goto-char (+ pos 3))
(skip-chars-forward "A-Za-z0-9_")
(let ((name-end (point)))
(klammertext--set-match pos name-end
nil nil nil nil nil nil nil
(cons pos name-end) ; 8: @@@name opening
nil)
(goto-char name-end)
t))
;; name@@@ (named close) or the bare close of a compact @@@name@@@.
((klammertext--name-char-p before)
(let ((name-start (save-excursion
(goto-char pos)
(skip-chars-backward "A-Za-z0-9_")
(point))))
(if (and (> name-start (point-min))
(eq (char-before name-start) ?@))
(klammertext--set-match pos (+ pos 3) ; @@@name@@@ -> bare @@@
nil nil nil nil nil nil nil nil
(cons pos (+ pos 3)))
(klammertext--set-match name-start (+ pos 3) ; NAME@@@ named close
nil nil nil nil nil nil nil nil
(cons name-start (+ pos 3))))
(goto-char (+ pos 3))
t))
;; bare @@@ : unnamed closing delimiter
(t
(klammertext--set-match pos (+ pos 3)
nil nil nil nil nil nil nil nil
(cons pos (+ pos 3))) ; 9: bare @@@ close
(goto-char (+ pos 3))
t))))
;; --- The single scanning matcher ---------------------------------------
(defun klammertext--fontify (limit)
"Font-lock matcher: emit the next Klammertext token up to LIMIT.
Removed and literal-klammer regions are jumped over, so their interiors
are never re-interpreted."
(let ((result nil))
(while (and (not result)
(re-search-forward "[#@]" limit t))
(let* ((pos (1- (point)))
(ch (char-after pos)))
(setq result
(cond
((klammertext--escaped-p pos) nil) ; ^# or ^@
((eq ch ?#) (klammertext--emit-removal pos limit))
(t (klammertext--emit-klammer pos limit))))))
result))
(defvar klammertext-font-lock-keywords
'((klammertext--fontify
(1 'klammertext-marker-face t t)
(2 'klammertext-ignored-face t t)
(3 'klammertext-marker-face t t)
(4 'klammertext-klammer-open-face t t)
(5 'klammertext-klammer-close-face t t)
(6 'klammertext-def-open-face t t)
(7 'klammertext-def-close-face t t)
(8 'klammertext-system-open-face t t)
(9 'klammertext-system-close-face t t)))
"Font-lock keywords for `klammertext-mode'.")
;; --- show-paren support (klammer applications only) --------------------
;;
;; show-paren cannot use the syntax table for Klammertext (the same @ is both
;; open and close, delimiters are multi-character, and open/close is decided by
;; context), so matching is driven by `show-paren-data-function'. Only single-@
;; APPLICATION delimiters are matched: @name <-> its closing @ or name@. The
;; matcher steps over @@/@@@ runs, removed text, other literal spans and escaped
;; ^@; the abbreviated @name-arg form opens no span. A LITERAL klammer (one in
;; `klammertext-literal-klammers', e.g. @code) is matched by name — @code <->
;; code@ — with its verbatim content opaque, since a depth scan would miscount
;; unbalanced @ inside it. A named close name@ whose name disagrees with its
;; opening @name is reported as a mismatch.
(defun klammertext--at-run-end (pos)
"Return the position just after the run of @ that begins at POS."
(let ((p pos)) (while (eq (char-after p) ?@) (setq p (1+ p))) p))
(defun klammertext--next-app-delim (limit)
"From point, find the next single-@ application delimiter before LIMIT.
Step over @@/@@@ runs, removed text, literal spans, escaped ^@, and the
abbreviated @name-arg form (which opens no span). Move point past the
delimiter (or skipped region) and return (POS . KIND) with KIND `open or
`close, or nil when none is found."
(catch 'found
(while (re-search-forward "[@#]" limit t)
(let ((hit (1- (point))))
(cond
((klammertext--escaped-p hit)) ; ^@ / ^# : keep going
((eq (char-after hit) ?#) ; removal: step over it
(let ((next (char-after (1+ hit))))
(goto-char (cond ((eq next ?#) (point-max))
((eq next ?\[) (klammertext--block-end (+ hit 2)))
((memq next '(?+ ?/ ?-)) (1+ hit))
(t (line-end-position))))))
((eq (char-after (1+ hit)) ?@) ; @@ / @@@ : step over run
(goto-char (klammertext--at-run-end hit)))
((klammertext--name-char-p (char-after (1+ hit))) ; @name : opening?
(goto-char (1+ hit))
(skip-chars-forward "A-Za-z0-9_")
(let ((name (buffer-substring-no-properties (1+ hit) (point))))
(cond
((member name klammertext-literal-klammers) ; literal span: skip
(let ((close (concat (regexp-quote name) "@")))
(unless (re-search-forward close nil t) (goto-char (point-max)))))
((eq (char-after) ?-)) ; @name-arg : no span
(t (throw 'found (cons hit 'open))))))
(t ; name@ / bare @ : closing
(goto-char (1+ hit))
(throw 'found (cons hit 'close))))))
nil))
(defun klammertext--match-forward (open-pos)
"OPEN-POS is the @ of an opening application. Return the matching close @
position, or nil if unbalanced."
(save-excursion
(goto-char (1+ open-pos))
(skip-chars-forward "A-Za-z0-9_") ; past the opening name
(let ((depth 1) (result nil) (go t))
(while (and go (> depth 0))
(let ((d (klammertext--next-app-delim nil)))
(if (null d)
(setq go nil)
(if (eq (cdr d) 'open)
(setq depth (1+ depth))
(setq depth (1- depth))
(when (= depth 0) (setq result (car d)))))))
result)))
(defun klammertext--match-backward (close-pos)
"CLOSE-POS is the @ of a closing application. Return the matching open @
position, or nil if unbalanced. Scans forward from `point-min' with a stack."
(save-excursion
(goto-char (point-min))
(let ((stack nil) (result nil) (go t))
(while go
(let ((d (klammertext--next-app-delim (1+ close-pos))))
(cond
((null d) (setq go nil))
((eq (cdr d) 'open) (push (car d) stack))
(t (let ((open (pop stack)))
(when (= (car d) close-pos)
(setq result open go nil)))))))
result)))
(defun klammertext--app-delim-info (pos)
"If the char at POS is a single-@ application delimiter, return (POS . KIND)
with KIND `open or `close; else nil. The abbreviated @name-arg form (which
opens no span) returns nil."
(when (and (eq (char-after pos) ?@)
(not (eq (char-before pos) ?@))
(not (eq (char-after (1+ pos)) ?@))
(not (klammertext--escaped-p pos)))
(if (klammertext--name-char-p (char-after (1+ pos)))
(let ((name-end (save-excursion (goto-char (1+ pos))
(skip-chars-forward "A-Za-z0-9_")
(point))))
(unless (eq (char-after name-end) ?-)
(cons pos 'open)))
(cons pos 'close))))
(defun klammertext--open-name (open-pos)
"Name of the opening @name at OPEN-POS."
(save-excursion (goto-char (1+ open-pos))
(buffer-substring-no-properties
(point) (progn (skip-chars-forward "A-Za-z0-9_") (point)))))
(defun klammertext--close-name (close-pos)
"Name of a named close NAME@ at CLOSE-POS, or nil for a bare @ (incl. @name@)."
(save-excursion
(goto-char close-pos)
(let ((ns (progn (skip-chars-backward "A-Za-z0-9_") (point))))
(when (and (< ns close-pos)
(not (eq (char-before ns) ?@)))
(buffer-substring-no-properties ns close-pos)))))
(defun klammertext--paren-mismatch (open-pos close-pos)
"Non-nil if OPEN-POS/CLOSE-POS is unbalanced, or the named close disagrees
with the opening name."
(or (null open-pos) (null close-pos)
(let ((cname (klammertext--close-name close-pos)))
(and cname (not (string= cname (klammertext--open-name open-pos)))))))
(defun klammertext--literal-delim-name (pos kind)
"If the application delimiter at POS (KIND `open or `close) belongs to a
literal klammer (one in `klammertext-literal-klammers'), return its name;
else nil. A literal klammer must be closed with the full NAME@ form because
its content is verbatim, so its @NAME open and NAME@ close are matched by
name, not by depth counting."
(let ((name (if (eq kind 'open)
(klammertext--open-name pos)
(klammertext--close-name pos))))
(and name (member name klammertext-literal-klammers) name)))
(defun klammertext--literal-match-forward (open-pos name)
"Return the @ of the NAME@ that closes the literal @NAME at OPEN-POS, or nil.
The verbatim content is opaque, so we search for the literal close string."
(save-excursion
(goto-char (+ open-pos 1 (length name)))
(when (search-forward (concat name "@") nil t)
(1- (point)))))
(defun klammertext--literal-match-backward (close-pos name)
"Return the @ of the @NAME that opens the literal NAME@ whose @ is at
CLOSE-POS, or nil. Literal spans do not nest, so the nearest preceding real
@NAME is the opener."
(save-excursion
(goto-char close-pos)
(let ((open-str (concat "@" name)) (result nil))
(while (and (not result) (search-backward open-str nil t))
(let ((op (point)))
(unless (or (eq (char-before op) ?@) ; @@NAME = definition
(klammertext--escaped-p op))
(setq result op))))
result)))
(defun klammertext--app-match (pos kind)
"Return the matching application delimiter for the delimiter at POS of KIND
\(`open or `close), or nil.
A literal klammer (in `klammertext-literal-klammers') matches by name
(@NAME <-> NAME@) with its content opaque; other klammers match by depth."
(let ((lit (klammertext--literal-delim-name pos kind)))
(cond
((and lit (eq kind 'open)) (klammertext--literal-match-forward pos lit))
((and lit (eq kind 'close)) (klammertext--literal-match-backward pos lit))
((eq kind 'open) (klammertext--match-forward pos))
(t (klammertext--match-backward pos)))))
(defun klammertext--report-mismatch (open-pos close-pos)
"Show a minibuffer message describing a klammer application mismatch.
Either position may be nil (an unbalanced delimiter)."
(message "%s"
(cond
((null close-pos)
(format "Klammertext: opening @%s has no matching close"
(klammertext--open-name open-pos)))
((null open-pos)
"Klammertext: closing delimiter has no matching open")
(t
(format "Klammertext: closing %s@ does not match opening @%s"
(or (klammertext--close-name close-pos) "?")
(klammertext--open-name open-pos))))))
(defun klammertext--show-paren-data ()
"`show-paren-data-function' for klammer applications, both directions.
Returns (HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH) or nil, and reports
any mismatch in the minibuffer."
(let* ((p (point))
(info (or (klammertext--app-delim-info p)
(and (> p (point-min))
(klammertext--app-delim-info (1- p))))))
(when info
(let* ((dpos (car info)) (kind (cdr info))
(match (klammertext--app-match dpos kind))
(open (if (eq kind 'open) dpos match))
(close (if (eq kind 'open) match dpos))
(mism (klammertext--paren-mismatch open close)))
(when mism (klammertext--report-mismatch open close))
(list dpos (1+ dpos) match (and match (1+ match)) mism)))))
;; --- Interactive: jump to the matching application delimiter -----------
(defun klammertext-jump-to-match ()
"Jump to the matching klammer application delimiter.
On an opening @name, move to its closing @ or name@; on a close, move to the
opening @name. Uses the same matcher as `show-paren-mode'. The starting
position is pushed to the mark ring, so \\`C-u C-SPC' jumps back."
(interactive)
(let* ((p (point))
(info (or (klammertext--app-delim-info p)
(and (> p (point-min))
(klammertext--app-delim-info (1- p))))))
(unless info
(user-error "Point is not on a klammer application delimiter (@)"))
(let* ((dpos (car info)) (kind (cdr info))
(match (klammertext--app-match dpos kind)))
(unless match
(user-error "No matching delimiter for this %s klammer"
(if (eq kind 'open) "opening" "closing")))
(push-mark nil t)
(goto-char match))))
;; --- The mode ----------------------------------------------------------
;;;###autoload
(define-derived-mode klammertext-mode text-mode "Klammertext"
"Major mode for editing Klammertext files."
(setq-local font-lock-multiline t)
(setq-local font-lock-defaults '(klammertext-font-lock-keywords))
;; Match klammer application delimiters with `show-paren-mode' (which must be
;; enabled separately; it is on by default in Emacs 28+).
(setq-local show-paren-data-function #'klammertext--show-paren-data)
;; Show a mismatched delimiter in bright red rather than the default purple
;; `show-paren-mismatch', but only in Klammertext buffers.
(setq-local face-remapping-alist
(cons '(show-paren-mismatch klammertext-mismatch-face)
face-remapping-alist)))
(define-key klammertext-mode-map (kbd "C-c C-j") #'klammertext-jump-to-match)
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.kt\\'" . klammertext-mode))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.k\\'" . klammertext-mode))
(provide 'klammertext-mode)
;;; klammertext-mode.el ends here

View File

@@ -0,0 +1,60 @@
// Klammertext colors for the "Breakers" scheme (light ground).
// One hue system across all schemes: application = blue, definition =
// green, system = orange; each opens bright and its close is 80%% of the
// open (a klammer "begins bright and gets dark"). Shown at full intensity
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
// forced to normal style. Merged onto Breakers by filename; recolors only
// .klammertext scopes. (The highlighting was first developed as an Emacs
// major mode; see Klammertext_in_Sublime_Text.md.)
//
// #999999 removed text (Breakers's comment grey)
// #994040 removal markers
// #528599 @name open blue
// #426a7a name@ close darker blue
// #758b55 @@name open green
// #5e7044 name@@ close darker green
// #996743 @@@name open orange
// #7a5236 name@@@ close darker orange
{
"name": "Breakers",
"rules": [
{
"scope": "comment.line.klammertext, comment.block.klammertext",
"foreground": "#999999"
},
{
"scope": "punctuation.definition.comment.klammertext",
"foreground": "#994040"
},
{
"scope": "entity.name.function.begin.klammertext",
"foreground": "#528599",
"font_style": ""
},
{
"scope": "entity.name.function.end.klammertext",
"foreground": "#426a7a",
"font_style": ""
},
{
"scope": "storage.type.begin.klammertext",
"foreground": "#758b55",
"font_style": ""
},
{
"scope": "storage.type.end.klammertext",
"foreground": "#5e7044",
"font_style": ""
},
{
"scope": "keyword.control.begin.klammertext",
"foreground": "#996743",
"font_style": ""
},
{
"scope": "keyword.control.end.klammertext",
"foreground": "#7a5236",
"font_style": ""
}
]
}

View File

@@ -0,0 +1,60 @@
// Klammertext colors for the "Celeste" scheme (light ground).
// One hue system across all schemes: application = blue, definition =
// green, system = orange; each opens bright and its close is 80%% of the
// open (a klammer "begins bright and gets dark"). Shown at full intensity
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
// forced to normal style. Merged onto Celeste by filename; recolors only
// .klammertext scopes. (The highlighting was first developed as an Emacs
// major mode; see Klammertext_in_Sublime_Text.md.)
//
// #9a9a9a removed text (Celeste's comment grey)
// #994040 removal markers
// #528599 @name open blue
// #426a7a name@ close darker blue
// #758b55 @@name open green
// #5e7044 name@@ close darker green
// #996743 @@@name open orange
// #7a5236 name@@@ close darker orange
{
"name": "Celeste",
"rules": [
{
"scope": "comment.line.klammertext, comment.block.klammertext",
"foreground": "#9a9a9a"
},
{
"scope": "punctuation.definition.comment.klammertext",
"foreground": "#994040"
},
{
"scope": "entity.name.function.begin.klammertext",
"foreground": "#528599",
"font_style": ""
},
{
"scope": "entity.name.function.end.klammertext",
"foreground": "#426a7a",
"font_style": ""
},
{
"scope": "storage.type.begin.klammertext",
"foreground": "#758b55",
"font_style": ""
},
{
"scope": "storage.type.end.klammertext",
"foreground": "#5e7044",
"font_style": ""
},
{
"scope": "keyword.control.begin.klammertext",
"foreground": "#996743",
"font_style": ""
},
{
"scope": "keyword.control.end.klammertext",
"foreground": "#7a5236",
"font_style": ""
}
]
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!--
Comment toggling for Klammertext. Companion to Klammertext.sublime-syntax.
Ctrl+/ (toggle_comment) -> line removal: "# "
Ctrl+Shift+/ (toggle_comment block:true) -> block removal: "#[ ... ]#"
These map onto Klammertext's own text-removal syntax:
# removes to end of line (the line comment)
#[ ... ]# removes enclosed text, nestable (the block comment)
-->
<key>name</key>
<string>Comments</string>
<key>scope</key>
<string>text.klammertext</string>
<key>settings</key>
<dict>
<key>shellVariables</key>
<array>
<dict>
<key>name</key>
<string>TM_COMMENT_START</string>
<key>value</key>
<string># </string>
</dict>
<dict>
<key>name</key>
<string>TM_COMMENT_START_2</string>
<key>value</key>
<string>#[</string>
</dict>
<dict>
<key>name</key>
<string>TM_COMMENT_END_2</string>
<key>value</key>
<string>]#</string>
</dict>
</array>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
// Klammertext key bindings.
//
// Binds "jump to matching klammer delimiter" (the companion Klammertext.py
// command) to Ctrl+M — Sublime's own "go to matching bracket" key, repurposed
// for klammers, since the built-in cannot match context-dependent @ pairs.
//
// The "selector" context confines the binding to Klammertext files, so Ctrl+M
// keeps its normal meaning everywhere else.
//
// macOS users may prefer "super+m"; change the "keys" value below. This file
// (no platform suffix) is loaded on all platforms.
[
{
"keys": ["ctrl+m"],
"command": "klammertext_jump_to_match",
"context": [
{ "key": "selector", "operator": "equal", "operand": "text.klammertext" }
]
}
]

View File

@@ -0,0 +1,471 @@
# Klammertext.py
#
# Sublime Text plugin for klammer APPLICATION (@) delimiters. Two features,
# both ports of doc/emacs/klammertext-mode.el, both reusing one matcher:
#
# 1. Jump between an opening and its close — the Sublime equivalent of the
# Emacs mode's `klammertext-jump-to-match' (bound C-c C-j). Command name
# klammertext_jump_to_match; keybinding in Default.sublime-keymap.
#
# 2. Live highlighting of the matching delimiter as the caret sits on one —
# the equivalent of the Emacs mode's show-paren support. Implemented as a
# ViewEventListener (see KlammertextMatchHighlighter at the bottom); no
# language server is involved. A mismatched named close or an unbalanced
# delimiter is highlighted in red with a status-bar message, mirroring the
# Emacs mode's klammertext-mismatch-face + minibuffer report.
#
# This is the companion to Klammertext.sublime-syntax. The syntax file only
# colors tokens; a tokenizer cannot match context-dependent delimiters, so the
# jump is implemented here as a TextCommand. The keybinding lives in the
# companion Default.sublime-keymap.
#
# Command name (for keymaps / the command palette): klammertext_jump_to_match
#
# ---------------------------------------------------------------------------
# What it does (a direct port of the elisp matcher):
# * On an opening @name, move to its closing @ or name@.
# * On a close (bare @ or name@), move to the opening @name.
# * Triggers when the caret is ON the @ or immediately AFTER it (the same
# on-or-just-after rule the Emacs command uses).
# * Only single-@ APPLICATION delimiters match. @@/@@@ runs, removed text
# (#, ##, #[...]#), escaped ^@, and literal-klammer spans (@code ... code@)
# are stepped over, exactly as in the Emacs mode. The abbreviated
# @name-arg form opens no span.
# * Works at every caret when there are multiple selections.
#
# Literal klammers (identical to C-c C-j): a @code ... code@ span is opaque.
# The general depth scan still steps over such a span WHOLESALE when matching
# some OTHER klammer, so verbatim @ inside it never miscount. A literal
# klammer's OWN delimiters are matched BY NAME rather than by depth (see
# app_match): @code jumps to the next code@, and code@ to the nearest preceding
# @code — correct even when the content holds unbalanced @, e.g. @code x @ y
# code@. LITERAL_KLAMMERS lists these names; keep it in sync with the '@code'
# handling in Klammertext.sublime-syntax.
#
# LITERAL_KLAMMERS must stay in sync with the literal klammers recognized in
# Klammertext.sublime-syntax (seeded there as @code). The Emacs mode keeps this
# list in the `klammertext-literal-klammers' defcustom; a plugin has no access
# to it, so it is duplicated here.
import sublime
import sublime_plugin
# Klammer names whose content is a literal argument (verbatim interior).
#
# SYNC: this list is one of three copies that must agree. When you add or
# remove a literal klammer, mirror it in all three:
# * klammertext-literal-klammers in doc/emacs/klammertext-mode.el (the source
# of truth; a Sublime syntax/plugin cannot read that Emacs defcustom)
# * LITERAL_KLAMMERS here
# * the @NAME literal rule + literal_NAME context in Klammertext.sublime-syntax
# All three are currently seeded with just "code".
LITERAL_KLAMMERS = set(["code"])
# --- pure helpers (operate on the whole buffer as a string) ----------------
def name_char_p(ch):
"""True if CH can be part of a klammer name (letter, digit or _).
A hyphen is NOT a name char: @name-arg1 ends the name at the first hyphen."""
if ch is None:
return False
return (('a' <= ch <= 'z') or ('A' <= ch <= 'Z')
or ('0' <= ch <= '9') or ch == '_')
def escaped_p(s, pos):
"""True if the char at POS is escaped by an odd run of ^ before it.
In Klammertext ^# and ^@ are literal, so such a char is not a delimiter."""
n = 0
i = pos - 1
while i >= 0 and s[i] == '^':
n += 1
i -= 1
return (n % 2) == 1
def block_end(s, frm):
"""Index just after the ]# that closes a #[ block opened at FROM (the index
just after the opening #[). Counts nested #[ ... ]#; len(s) if unclosed."""
depth = 1
i = frm
n = len(s)
while depth > 0:
a = s.find('#[', i)
b = s.find(']#', i)
if a == -1 and b == -1:
return n
if b == -1 or (a != -1 and a < b):
depth += 1
i = a + 2
else:
depth -= 1
i = b + 2
return i
def at_run_end(s, pos):
"""Index just after the run of @ that begins at POS."""
p = pos
n = len(s)
while p < n and s[p] == '@':
p += 1
return p
def next_app_delim(s, i, limit):
"""From index I, find the next single-@ application delimiter before LIMIT.
Step over @@/@@@ runs, removed text, literal spans, escaped ^@, and the
abbreviated @name-arg form. Return (pos, kind, next_i) with kind 'open' or
'close' and next_i the index to resume from, or None when none is found."""
n = len(s)
if limit is None:
limit = n
while i < limit:
# find next @ or # at or after i (emacs re-search-forward "[@#]")
j = i
while j < limit and s[j] != '@' and s[j] != '#':
j += 1
if j >= limit:
return None
hit = j
i = hit + 1 # default: advance past the hit
if escaped_p(s, hit): # ^@ / ^# : keep going
continue
nxt = s[hit + 1] if hit + 1 < n else None
if s[hit] == '#': # removal: step over it
if nxt == '#':
i = n
elif nxt == '[':
i = block_end(s, hit + 2)
elif nxt in ('+', '/', '-'):
i = hit + 1
else: # to end of line
eol = s.find('\n', hit)
i = n if eol == -1 else eol
continue
# s[hit] == '@'
if nxt == '@': # @@ / @@@ : step over the run
i = at_run_end(s, hit)
continue
if name_char_p(nxt): # @name : opening?
k = hit + 1
while k < n and name_char_p(s[k]):
k += 1
name = s[hit + 1:k]
after = s[k] if k < n else None
if name in LITERAL_KLAMMERS: # literal span: skip to its close
close = name + '@'
idx = s.find(close, k)
i = n if idx == -1 else idx + len(close)
continue
elif after == '-': # @name-arg : opens no span
i = k
continue
else:
return (hit, 'open', k)
else: # name@ / bare @ : closing
return (hit, 'close', hit + 1)
return None
def match_forward(s, open_pos):
"""OPEN_POS is the @ of an opening application. Return the matching close @
index, or None if unbalanced."""
n = len(s)
i = open_pos + 1
while i < n and name_char_p(s[i]): # past the opening name
i += 1
depth = 1
while depth > 0:
d = next_app_delim(s, i, None)
if d is None:
return None
pos, kind, nxt = d
i = nxt
if kind == 'open':
depth += 1
else:
depth -= 1
if depth == 0:
return pos
return None
def match_backward(s, close_pos):
"""CLOSE_POS is the @ of a closing application. Return the matching open @
index, or None if unbalanced. Scans forward from 0 with a stack."""
stack = []
i = 0
limit = close_pos + 1
while True:
d = next_app_delim(s, i, limit)
if d is None:
return None
pos, kind, nxt = d
i = nxt
if kind == 'open':
stack.append(pos)
else:
open_pos = stack.pop() if stack else None
if pos == close_pos:
return open_pos
def app_delim_info(s, pos):
"""If the char at POS is a single-@ application delimiter, return
(pos, kind) with kind 'open' or 'close'; else None. The abbreviated
@name-arg form (which opens no span) returns None."""
n = len(s)
if not (0 <= pos < n):
return None
if s[pos] != '@':
return None
if pos > 0 and s[pos - 1] == '@':
return None
if pos + 1 < n and s[pos + 1] == '@':
return None
if escaped_p(s, pos):
return None
nxt = s[pos + 1] if pos + 1 < n else None
if name_char_p(nxt):
k = pos + 1
while k < n and name_char_p(s[k]):
k += 1
after = s[k] if k < n else None
if after == '-':
return None
return (pos, 'open')
return (pos, 'close')
# --- name / mismatch helpers (for the live highlighter) --------------------
def _name_forward(s, pos):
"""Index just past the run of name chars starting at POS."""
n = len(s)
k = pos
while k < n and name_char_p(s[k]):
k += 1
return k
def open_name(s, open_pos):
"""Name of the opening @name whose @ is at OPEN_POS."""
return s[open_pos + 1:_name_forward(s, open_pos + 1)]
def close_name(s, close_pos):
"""Name of a named close NAME@ whose @ is at CLOSE_POS, or None for a bare @
(including the compact @name@ form, whose name belongs to the opening)."""
ns = close_pos
while ns > 0 and name_char_p(s[ns - 1]):
ns -= 1
if ns < close_pos and (ns == 0 or s[ns - 1] != '@'):
return s[ns:close_pos]
return None
def paren_mismatch(s, open_pos, close_pos):
"""True if the pair is unbalanced (either side None) or the named close
disagrees with the opening name."""
if open_pos is None or close_pos is None:
return True
cname = close_name(s, close_pos)
return cname is not None and cname != open_name(s, open_pos)
def token_region(s, pos, kind):
"""(start, end) of the whole delimiter token whose @ is at POS.
Opening: @ plus its name. Named close: the name plus @. Bare @: just @."""
if kind == 'open':
return (pos, _name_forward(s, pos + 1))
ns = pos
while ns > 0 and name_char_p(s[ns - 1]):
ns -= 1
if ns < pos and (ns == 0 or s[ns - 1] != '@'):
return (ns, pos + 1) # named close NAME@
return (pos, pos + 1) # bare @ (or @name@)
# --- matching dispatch: literal klammers by name, others by depth ----------
def literal_delim_name(s, pos, kind):
"""If the application delimiter at POS (kind 'open'/'close') belongs to a
literal klammer (name in LITERAL_KLAMMERS), return its name; else None.
A literal klammer's @NAME open and NAME@ close are matched by name, not by
depth counting, because its content is verbatim."""
name = open_name(s, pos) if kind == 'open' else close_name(s, pos)
if name and name in LITERAL_KLAMMERS:
return name
return None
def literal_match_forward(s, open_pos, name):
"""Index of the @ of the NAME@ that closes the literal @NAME at OPEN_POS, or
None. The content is opaque, so search for the literal close string."""
start = open_pos + 1 + len(name)
idx = s.find(name + '@', start)
return idx + len(name) if idx != -1 else None
def literal_match_backward(s, close_pos, name):
"""Index of the @ of the @NAME that opens the literal NAME@ whose @ is at
CLOSE_POS, or None. Literal spans do not nest, so the nearest preceding
real @NAME is the opener (not @@NAME, and not escaped)."""
open_str = '@' + name
end = close_pos
while True:
idx = s.rfind(open_str, 0, end)
if idx == -1:
return None
before = s[idx - 1] if idx > 0 else None
if before != '@' and not escaped_p(s, idx):
return idx
end = idx
def app_match(s, pos, kind):
"""Matching application delimiter for the delimiter at POS of KIND
('open'/'close'), or None. A literal klammer matches by name (@NAME <->
NAME@) with content opaque; other klammers match by depth."""
lit = literal_delim_name(s, pos, kind)
if lit is not None:
return (literal_match_forward(s, pos, lit) if kind == 'open'
else literal_match_backward(s, pos, lit))
return match_forward(s, pos) if kind == 'open' else match_backward(s, pos)
# --- the command -----------------------------------------------------------
class KlammertextJumpToMatchCommand(sublime_plugin.TextCommand):
"""Jump between a klammer application's opening and closing delimiter.
Sublime equivalent of the Emacs mode's C-c C-j."""
def run(self, edit):
view = self.view
s = view.substr(sublime.Region(0, view.size()))
new_regions = []
moved = False
message = None
for region in view.sel():
p = region.b
info = app_delim_info(s, p)
if info is None and p > 0:
info = app_delim_info(s, p - 1)
if info is None:
new_regions.append(region)
message = "point is not on a klammer application delimiter (@)"
continue
dpos, kind = info
match = app_match(s, dpos, kind)
if match is None:
new_regions.append(region)
message = ("no matching delimiter for this %s klammer"
% ("opening" if kind == 'open' else "closing"))
continue
new_regions.append(sublime.Region(match, match))
moved = True
view.sel().clear()
for r in new_regions:
view.sel().add(r)
if moved:
view.show(view.sel()[0].b)
elif message:
sublime.status_message("Klammertext: " + message)
def is_enabled(self):
# Only meaningful in Klammertext buffers.
return self.view.match_selector(0, "text.klammertext")
# --- live matched-delimiter highlighting (show-paren equivalent) -----------
class KlammertextMatchHighlighter(sublime_plugin.ViewEventListener):
"""Highlight the matching klammer application delimiter as the caret sits
on one. The Sublime equivalent of the Emacs mode's show-paren support —
driven by cursor movement, reusing the same context-sensitive matcher.
A matched pair is boxed (region.bluish); a mismatch or unbalanced delimiter
is boxed in red (region.redish) with a status-bar message. Both the token
under the caret and its match are boxed; the Emacs mode highlights only the
single @ character, but boxing the whole @name / name@ reads better here.
To highlight only the far delimiter, drop the first region in _update()."""
MATCH_KEY = 'klammertext_paren_match'
MISMATCH_KEY = 'klammertext_paren_mismatch'
@classmethod
def is_applicable(cls, settings):
return str(settings.get('syntax', '')).endswith('Klammertext.sublime-syntax')
def __init__(self, view):
super().__init__(view)
self._change_count = -1
self._text = ''
def _buffer(self):
# Re-read the buffer only when it has actually changed, so plain cursor
# movement over a large file does not re-copy the whole document.
cc = self.view.change_count()
if cc != self._change_count:
self._text = self.view.substr(sublime.Region(0, self.view.size()))
self._change_count = cc
return self._text
def on_selection_modified_async(self):
self._update()
def on_activated_async(self):
self._update()
def _clear(self):
self.view.erase_regions(self.MATCH_KEY)
self.view.erase_regions(self.MISMATCH_KEY)
def _update(self):
view = self.view
sel = view.sel()
if len(sel) == 0:
self._clear()
return
p = sel[0].b
s = self._buffer()
info = app_delim_info(s, p)
if info is None and p > 0:
info = app_delim_info(s, p - 1)
if info is None:
self._clear()
return
dpos, kind = info
match = app_match(s, dpos, kind)
open_pos = dpos if kind == 'open' else match
close_pos = match if kind == 'open' else dpos
mism = paren_mismatch(s, open_pos, close_pos)
regions = [sublime.Region(*token_region(s, dpos, kind))]
if match is not None:
other_kind = 'close' if kind == 'open' else 'open'
regions.append(sublime.Region(*token_region(s, match, other_kind)))
flags = sublime.DRAW_NO_FILL
if mism:
view.erase_regions(self.MATCH_KEY)
view.add_regions(self.MISMATCH_KEY, regions, 'region.redish', '', flags)
if match is None:
if kind == 'open':
msg = "opening @%s has no matching close" % open_name(s, open_pos)
else:
msg = "closing delimiter has no matching open"
else:
msg = ("closing %s@ does not match opening @%s"
% (close_name(s, close_pos) or '?', open_name(s, open_pos)))
sublime.status_message("Klammertext: " + msg)
else:
view.erase_regions(self.MISMATCH_KEY)
view.add_regions(self.MATCH_KEY, regions, 'region.bluish', '', flags)

View File

@@ -0,0 +1,183 @@
%YAML 1.2
---
# Klammertext.sublime-syntax
#
# Sublime Text syntax highlighting for Klammertext (.kt and .k files).
# A port of the Emacs major mode doc/emacs/klammertext-mode.el.
#
# ---------------------------------------------------------------------------
# What it highlights (mirrors the Emacs mode's eight token classes):
#
# Text removal (#):
# # ... remove to end of line (marker + removed text)
# ## ... remove to end of file (marker + removed text)
# #[ ... ]# remove enclosed text, nestable (markers + removed text)
# #- #+ #/ whitespace operators: NOT removals, left unhighlighted
# (matched only so the '#' above does not eat the line)
#
# Klammer applications (@), definitions (@@), system commands (@@@):
# @name @@name @@@name opening (@ and name are one unit)
# name@ name@@ name@@@ named closing
# @ @@ @@@ bare closing
#
# Escapes: ^@ ^# ^| ^^ the caret makes the next character literal, so it
# is consumed and NOT treated as a delimiter. Left unscoped, to
# match the Emacs mode, which shows escaped characters as ordinary
# text. (A run of carets pairs left-to-right: ^^ is a literal
# caret, a leftover single ^ escapes the following character —
# the '\^.' rule reproduces exactly that parity.)
#
# Literal klammers: @code ... code@ interior is verbatim (no # or @
# interpreted). To add another literal klammer 'foo', copy the
# '@code' rule and the 'literal_code' context below, replacing
# code -> foo.
#
# SYNC: the literal-klammer set is duplicated in three places that
# must agree (a .sublime-syntax file is static and cannot read the
# Emacs defcustom). When you add or remove one, mirror it in all:
# * klammertext-literal-klammers in
# doc/emacs/klammertext-mode.el (the source of truth)
# * LITERAL_KLAMMERS in Klammertext.py
# * the @NAME rule + literal_NAME context here
# All three are currently seeded with just 'code'.
#
# ---------------------------------------------------------------------------
# How open vs. close is decided (the same rule the Emacs scanner uses):
# * a delimiter whose NAME follows the @-run (@name) is an OPENING;
# * a bare @-run, or one whose NAME precedes it (name@), is a CLOSING.
# Because this tokenizer runs left-to-right, an opening consumes "@name" as one
# unit, so a trailing bare @ in the compact form @name@ is naturally a close.
# The (?![A-Za-z0-9_@]) look-ahead on every closing keeps "foo@bar" correct:
# @ is followed by a name, so it opens @bar and 'foo' stays plain text.
#
# ---------------------------------------------------------------------------
# Scope -> color. Colors live in the color scheme, not here. The package ships
# additive .sublime-color-scheme overrides for all five of Sublime's built-in
# schemes (Breakers, Celeste, Mariana, Monokai, Sixteen); each merges onto its
# scheme by filename and recolors only .klammertext scopes. They use one hue
# system — application blue, definition green, system orange, each opening bright
# and its close the same hue darker — shown at full intensity on dark grounds and
# scaled down on light grounds. Exact values are in each override's header.
#
# Without a matching override (e.g. a third-party scheme) a stock scheme still
# gives a meaningful default from these scope names: three klammer-family colors
# (function / storage / keyword), muted removed text (comment), plain escapes.
# To get the full palette on another scheme, copy one of the shipped overrides
# to <SchemeName>.sublime-color-scheme.
#
# ---------------------------------------------------------------------------
# Install: put this file — together with its companions Klammertext.py,
# Default.sublime-keymap and Comments.tmPreferences — in a dedicated package
# folder named 'Klammertext' under Packages/ (Preferences -> Browse Packages
# opens Packages/):
# ~/.config/sublime-text/Packages/Klammertext/ (Linux)
# ~/Library/Application Support/Sublime Text/Packages/Klammertext/ (macOS)
# A dedicated folder (not Packages/User/) keeps the bundled keymap from
# merging into your personal one. Sublime picks it all up live and applies
# the syntax to .kt and .k files. (The syntax file alone also works from
# Packages/User/ if you only want highlighting.)
#
# ---------------------------------------------------------------------------
# Known differences from the Emacs mode (deliberate, matching its own limits):
# * @@ and @@@ definition BODIES are highlighted as ordinary Klammertext,
# not treated specially — same as the Emacs mode.
# * Delimiter MATCHING (jump + live highlight) is not in this syntax file —
# Sublime's built-in bracket matching needs fixed character pairs, which @
# (both open and close, decided by context) cannot provide. It lives in
# the companion Klammertext.py instead: klammertext_jump_to_match (C-c C-j
# equivalent) and a ViewEventListener that highlights the matching
# delimiter as the caret moves (show-paren equivalent), both reusing one
# context-sensitive matcher. This is a plugin concern, not a tokenizer one.
# * Comment toggling is provided by the companion Comments.tmPreferences:
# Ctrl-/ inserts '# ' (line removal), Ctrl-Shift-/ wraps in '#[ ... ]#'
# (block removal).
name: Klammertext
file_extensions:
- kt
- k
scope: text.klammertext
version: 2
variables:
# A klammer name: letters, digits, underscore. A hyphen is NOT a name char
# (the abbreviated form @name-arg1-arg2 ends the name at the first hyphen).
name: '[A-Za-z0-9_]+'
# A closing delimiter must not be followed by a name char (that would be an
# opening @name) or another @ (that would be a longer @-run).
not_delim: '(?![A-Za-z0-9_@])'
contexts:
main:
# --- escapes: ^X makes X literal; consumed so # / @ are not delimiters ---
- match: '\^.'
# --- text removal (#) ---
- match: '##'
scope: punctuation.definition.comment.klammertext
push: removal_file
- match: '#\['
scope: punctuation.definition.comment.klammertext
push: removal_block
# whitespace operators #- #+ #/ (with optional count): not removals.
# Matched (and left unscoped) so the '#' line rule below does not consume
# the rest of the line. Add a scope here if you would rather color them.
- match: '#[-+/]\d*'
- match: '#'
scope: punctuation.definition.comment.klammertext
push: removal_line
# --- literal klammer: interior is verbatim (seeded default: @code) ---
- match: '@code(?![A-Za-z0-9_])'
scope: entity.name.function.begin.klammertext
push: literal_code
# --- system / target commands @@@ ---
- match: '@@@{{name}}'
scope: keyword.control.begin.klammertext # @@@name opening
- match: '@@@{{not_delim}}'
scope: keyword.control.end.klammertext # bare @@@ close
- match: '{{name}}@@@{{not_delim}}'
scope: keyword.control.end.klammertext # name@@@ named close
# --- klammer definitions @@ ---
- match: '@@{{name}}'
scope: storage.type.begin.klammertext # @@name opening
- match: '@@{{not_delim}}'
scope: storage.type.end.klammertext # bare @@ close
- match: '{{name}}@@{{not_delim}}'
scope: storage.type.end.klammertext # name@@ named close
# --- klammer applications @ ---
- match: '@{{name}}'
scope: entity.name.function.begin.klammertext # @name opening
- match: '@{{not_delim}}'
scope: entity.name.function.end.klammertext # bare @ close
- match: '{{name}}@{{not_delim}}'
scope: entity.name.function.end.klammertext # name@ named close
# rest of line is removed
removal_line:
- meta_scope: comment.line.klammertext
- match: '\n'
pop: true
# rest of file is removed (## never closes)
removal_file:
- meta_scope: comment.block.klammertext
# #[ ... ]# removed, nestable
removal_block:
- meta_scope: comment.block.klammertext
- match: '#\['
scope: punctuation.definition.comment.klammertext
push: removal_block
- match: '\]#'
scope: punctuation.definition.comment.klammertext
pop: true
# @code ... code@ — interior verbatim (unscoped), only the close ends it
literal_code:
- match: 'code@'
scope: entity.name.function.end.klammertext
pop: true

View File

@@ -0,0 +1,102 @@
# Klammertext for Sublime Text
A Sublime Text port of the Emacs major mode for Klammertext
(`doc/emacs/klammertext-mode.el`). It brings syntax highlighting, delimiter
matching, and comment toggling to `.kt` and `.k` files. Behavior mirrors the
Emacs mode closely; where the two intentionally differ, the file headers say so.
## Files
| File | Purpose |
|------|---------|
| `Klammertext.sublime-syntax` | Syntax highlighting. Colors the text-removal constructs (`#`, `##`, `#[...]#`) and the three `@`-tiers — application `@`, definition `@@`, system `@@@` — each as an opening vs. a close, plus `^`-escapes and verbatim `@code ... code@` spans. |
| `Klammertext.py` | Plugin with two features that share one context-sensitive matcher: jump between an opening and its close, and live highlighting of the matching delimiter as the caret moves (mismatched or unbalanced delimiters flag in red). |
| `Default.sublime-keymap` | Binds jump-to-match to **Ctrl+M**, scoped to Klammertext files. |
| `Comments.tmPreferences` | Comment toggling: **Ctrl+/** inserts `# ` (line removal), **Ctrl+Shift+/** wraps in `#[ ... ]#` (block removal). |
| `Breakers` / `Celeste` / `Mariana` / `Monokai` / `Sixteen` `.sublime-color-scheme` | Color overrides for Sublime's five built-in schemes — one hue system, full intensity on the dark schemes, scaled down on the light ones. Additive: they recolor only the Klammertext delimiters and leave the rest of each scheme unchanged. |
| `Klammertext_in_Sublime_Text.md` | This file. |
## Installation
Put the files into a folder named `Klammertext` under Sublime's `Packages`
directory:
| Platform | Path |
|----------|------|
| Linux | `~/.config/sublime-text/Packages/Klammertext/` |
| macOS | `~/Library/Application Support/Sublime Text/Packages/Klammertext/` |
| Windows | `%AppData%\Sublime Text\Packages\Klammertext\` |
The quickest way to find it: **Preferences → Browse Packages…** opens the
`Packages` directory. Create the `Klammertext` folder there and copy the files
in. Sublime loads them live — no restart — and applies the syntax to `.kt` and
`.k` files automatically.
Use a dedicated folder (not `Packages/User/`) so the bundled keymap does not
merge into your personal one. If you want highlighting only, the
`.sublime-syntax` file alone works from `Packages/User/`.
The plugin targets **Sublime Text 4**: the live-highlight colors use Sublime's
adaptive `region.*` scopes, which were added in ST4.
## Features and keys
| Trigger | Action |
|---------|--------|
| open a `.kt` / `.k` file | Syntax highlighting (automatic) |
| **Ctrl+M** | Jump between a klammer application's opening and closing `@` (equivalent of the Emacs mode's `C-c C-j`) |
| caret on a klammer `@` | The matching delimiter boxes automatically; a name mismatch or unbalanced delimiter boxes in red with a status-bar message (equivalent of `show-paren-mode`) |
| **Ctrl+/** | Toggle line comment (`#`) |
| **Ctrl+Shift+/** | Toggle block comment (`#[ ... ]#`) |
Ctrl+M is Sublime's own "go to matching bracket" key, reused here because the
built-in cannot match Klammertext's context-dependent `@`. macOS users who
prefer `super+m` can change it in `Default.sublime-keymap`.
## Colors
Colors are installed automatically for all five of Sublime's built-in schemes.
Each `*.sublime-color-scheme` file (Breakers, Celeste, Mariana, Monokai,
Sixteen) is an *additive override*: Sublime merges it onto the matching scheme
by filename, recoloring only the Klammertext delimiters and leaving everything
else untouched. There is nothing to set up.
All five share one hue system — application blue, definition green, system
orange, each opening bright and its close the same hue darker — shown at full
intensity on the dark schemes (Monokai, Mariana) and scaled down for contrast on
the light schemes (Breakers, Celeste, Sixteen). Removed text uses each scheme's
own comment grey.
For any other scheme — a legacy `.tmTheme` such as Solarized, or a third-party
scheme — copy one of the included files to `<Scheme Name>.sublime-color-scheme`
in the package folder (its name is shown at **Preferences → Settings** under
`color_scheme`), choosing a light or dark source file to match the ground. The
exact values are in each file's header comment.
## Keeping literal klammers in sync
Klammers whose content is verbatim (`@code ... code@`) are listed in three
places that must agree — a Sublime syntax/plugin cannot read the Emacs
defcustom, so the list is duplicated:
- `klammertext-literal-klammers` in `doc/emacs/klammertext-mode.el` (the source of truth)
- `LITERAL_KLAMMERS` in `Klammertext.py`
- the `@code` rule and `literal_code` context in `Klammertext.sublime-syntax`
All three are seeded with just `code`. When you add or remove a literal
klammer, change all three.
## Not included
Whole-file semantic validation — persistent error underlines when the cursor is
elsewhere, klammer-name completion, go-to-definition — is not part of this
package. That would need a language server (used through the Sublime LSP
package), a separate program, and is unrelated to the highlighting and matching
provided here.
## Troubleshooting
If the plugin does not seem to load, open **View → Show Console** for any error
message. Check that the files sit directly inside `Packages/Klammertext/` (not
a nested subfolder) and that the current file's syntax reads "Klammertext" in
the status bar at the bottom-right of the window.

View File

@@ -0,0 +1,60 @@
// Klammertext colors for the "Mariana" scheme (dark ground).
// One hue system across all schemes: application = blue, definition =
// green, system = orange; each opens bright and its close is 80%% of the
// open (a klammer "begins bright and gets dark"). Shown at full intensity
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
// forced to normal style. Merged onto Mariana by filename; recolors only
// .klammertext scopes. (The highlighting was first developed as an Emacs
// major mode; see Klammertext_in_Sublime_Text.md.)
//
// #a6acb9 removed text (Mariana's comment grey)
// #ff6b6b removal markers
// #89ddff @name open blue
// #6eb1cc name@ close darker blue
// #c3e88d @@name open green
// #9cba71 name@@ close darker green
// #ffab70 @@@name open orange
// #cc895a name@@@ close darker orange
{
"name": "Mariana",
"rules": [
{
"scope": "comment.line.klammertext, comment.block.klammertext",
"foreground": "#a6acb9"
},
{
"scope": "punctuation.definition.comment.klammertext",
"foreground": "#ff6b6b"
},
{
"scope": "entity.name.function.begin.klammertext",
"foreground": "#89ddff",
"font_style": ""
},
{
"scope": "entity.name.function.end.klammertext",
"foreground": "#6eb1cc",
"font_style": ""
},
{
"scope": "storage.type.begin.klammertext",
"foreground": "#c3e88d",
"font_style": ""
},
{
"scope": "storage.type.end.klammertext",
"foreground": "#9cba71",
"font_style": ""
},
{
"scope": "keyword.control.begin.klammertext",
"foreground": "#ffab70",
"font_style": ""
},
{
"scope": "keyword.control.end.klammertext",
"foreground": "#cc895a",
"font_style": ""
}
]
}

View File

@@ -0,0 +1,60 @@
// Klammertext colors for the "Monokai" scheme (dark ground).
// One hue system across all schemes: application = blue, definition =
// green, system = orange; each opens bright and its close is 80%% of the
// open (a klammer "begins bright and gets dark"). Shown at full intensity
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
// forced to normal style. Merged onto Monokai by filename; recolors only
// .klammertext scopes. (The highlighting was first developed as an Emacs
// major mode; see Klammertext_in_Sublime_Text.md.)
//
// #8a8272 removed text (Monokai's comment grey)
// #ff6b6b removal markers
// #89ddff @name open blue
// #6eb1cc name@ close darker blue
// #c3e88d @@name open green
// #9cba71 name@@ close darker green
// #ffab70 @@@name open orange
// #cc895a name@@@ close darker orange
{
"name": "Monokai",
"rules": [
{
"scope": "comment.line.klammertext, comment.block.klammertext",
"foreground": "#8a8272"
},
{
"scope": "punctuation.definition.comment.klammertext",
"foreground": "#ff6b6b"
},
{
"scope": "entity.name.function.begin.klammertext",
"foreground": "#89ddff",
"font_style": ""
},
{
"scope": "entity.name.function.end.klammertext",
"foreground": "#6eb1cc",
"font_style": ""
},
{
"scope": "storage.type.begin.klammertext",
"foreground": "#c3e88d",
"font_style": ""
},
{
"scope": "storage.type.end.klammertext",
"foreground": "#9cba71",
"font_style": ""
},
{
"scope": "keyword.control.begin.klammertext",
"foreground": "#ffab70",
"font_style": ""
},
{
"scope": "keyword.control.end.klammertext",
"foreground": "#cc895a",
"font_style": ""
}
]
}

View File

@@ -0,0 +1,60 @@
// Klammertext colors for the "Sixteen" scheme (light ground).
// One hue system across all schemes: application = blue, definition =
// green, system = orange; each opens bright and its close is 80%% of the
// open (a klammer "begins bright and gets dark"). Shown at full intensity
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
// forced to normal style. Merged onto Sixteen by filename; recolors only
// .klammertext scopes. (The highlighting was first developed as an Emacs
// major mode; see Klammertext_in_Sublime_Text.md.)
//
// #b8b8b8 removed text (Sixteen's comment grey)
// #994040 removal markers
// #528599 @name open blue
// #426a7a name@ close darker blue
// #758b55 @@name open green
// #5e7044 name@@ close darker green
// #996743 @@@name open orange
// #7a5236 name@@@ close darker orange
{
"name": "Sixteen",
"rules": [
{
"scope": "comment.line.klammertext, comment.block.klammertext",
"foreground": "#b8b8b8"
},
{
"scope": "punctuation.definition.comment.klammertext",
"foreground": "#994040"
},
{
"scope": "entity.name.function.begin.klammertext",
"foreground": "#528599",
"font_style": ""
},
{
"scope": "entity.name.function.end.klammertext",
"foreground": "#426a7a",
"font_style": ""
},
{
"scope": "storage.type.begin.klammertext",
"foreground": "#758b55",
"font_style": ""
},
{
"scope": "storage.type.end.klammertext",
"foreground": "#5e7044",
"font_style": ""
},
{
"scope": "keyword.control.begin.klammertext",
"foreground": "#996743",
"font_style": ""
},
{
"scope": "keyword.control.end.klammertext",
"foreground": "#7a5236",
"font_style": ""
}
]
}

View File

@@ -0,0 +1,45 @@
# This is a line comment removed to end of line, in the "ignored" color.
# The # marker is a different color from the text it removes.
#[ This is a block comment. It can span lines,
and #[ nest ]# like this. ]#
# --- Klammer applications (@) : opening @name vs. closing name@ / bare @ ---
@i italic @ @b bold @ @tt monospace @
@sup 2 | 3 @ # positional arguments separated by |
@sup-2-3 # the abbreviated form colors only the name
@link https://example.com :text a labelled link @
A named close is handy for long arguments: @section a long body here section@
# --- Klammer definitions (@@) and system commands (@@@) ---
@@mdlh : @i Material Definition Language Handbook @ @@
@@heading.html : *arg* @@
@@@target html | HTML output | options @@@
# --- Escapes: a caret makes the next character literal (shown as plain text) ---
^@ and ^# and ^^ and ^| are literal, not delimiters.
# --- Literal klammer: @code ... code@ interior is verbatim ---
# The stray @ and # below are NOT delimiters inside a literal span:
@code
if (a @ b) { return "# not a comment"; }
code@
# --- Whitespace operators (#- #+ #/) are not removals; shown as plain text ---
tight#-spacing gap#+3here break#/2line
# --- A deliberate MISMATCH: put the cursor on @open or close@ to see it turn ---
# --- red with a message (the names disagree); a matched pair boxes normally. ---
@open some content close@
## Everything from this line to the end of the file is removed (## = to EOF).
this trailing line is greyed out as removed text