VS Code: decoration-based matching, Ctrl+K bindings, README overhaul (from dev f84517152b7f)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 17:02:41 +02:00
parent 2eb16791d2
commit f84603ee19
10 changed files with 255 additions and 39 deletions

View File

@@ -283,6 +283,26 @@ class Server:
'kind': 1})
self.reply(msg_id, highlights)
def on_klammertext_matchInfo(self, msg_id, params):
"""Custom request: the full matching story for a cursor position —
token, matching token, and whether the pair mismatches. The VS Code
extension draws its live match/mismatch decorations from this
(documentHighlight is word-gated in VS Code, so a bare @ close would
never trigger it; and it cannot carry the mismatch flag)."""
uri = params['textDocument']['uri']
text = self.docs.get(uri, '')
m = KE.match_at(text, pos_to_offset(text, params['position']))
if m is None:
self.reply(msg_id, None)
return
self.reply(msg_id, {
'token': offsets_to_range(text, *m['token']),
'matchToken': (offsets_to_range(text, *m['match_token'])
if m['match_token'] is not None else None),
'mismatch': m['mismatch'],
'message': m['message'],
})
def on_textDocument_definition(self, msg_id, params):
uri = params['textDocument']['uri']
text = self.docs.get(uri, '')

View File

@@ -52,7 +52,23 @@ support built in, so there is no conflict out of the box; if you install a
Kotlin extension, the two will contend for `.kt` and you can decide per
file with the language-mode picker (or `files.associations`).
## What you get
## VS Code commands for Klammertext
| Command | Menu | Key | Cursor position |
| --- | --- | --- | --- |
| Format Document | Right-click | `Ctrl+Shift+I` | Anywhere in the document |
| Format Selection | Right-click | `Ctrl+K Ctrl+F` | Lines selected |
| Toggle Line Comment | Edit menu | `Ctrl+/` | In the line to remove with `#` |
| Toggle Block Comment | Edit menu | `Ctrl+Shift+A` | Region to remove selected (`#[ ... ]#`) |
| Klammertext: Jump to Matching Delimiter | Right-click | `Ctrl+K J` | On the opening `@name` or the closing `name@` / `@` |
| Klammertext: Align Table | Right-click | `Ctrl+K A` | Anywhere inside the `@table` |
| Delimiter diagnostics | Problems panel | — | Automatic, as you type |
All commands are also in the Command Palette (`Ctrl+Shift+P`). Keys shown
are the Linux defaults: the Klammertext commands use `Cmd+K` on macOS, and
the built-in formatting and comment keys differ per OS.
## Features
**Syntax highlighting** — the same token classes as the Emacs, Sublime
Text, and Vim support: text removal (`#`, `##`, nestable `#[ ... ]#`), the
@@ -65,42 +81,71 @@ Klammertext palette (application blue / definition green / system orange,
opens bright and closes darker), add `editor.tokenColorCustomizations`
rules for the `*.klammertext` scopes in your settings.
**Diagnostics** — unclosed and mismatched delimiters appear in the
Problems panel as you type.
**Formatting****Format Document** / **Format Selection** reindent
structurally (2 spaces per nesting level; bar runs and closing delimiters
sit at their opener's column; `@document` content stays at the margin;
verbatim `@code` interiors, `@eval` code, and removed text are never
touched). Reindentation is **explicit-only**: there is deliberately no
**Structural reindentation** — Format Document and Format Selection
reindent per the Klammertext convention: 2 spaces per nesting level; a
line beginning with a bar run or a closing delimiter sits at its opener's
column; `@document` content stays at the margin; verbatim `@code`
interiors, `@eval` code, and removed text are never touched.
Reindentation is **explicit-only**: there is deliberately no
format-on-type, because whitespace is content in Klammertext.
**Delimiter matching** — with the cursor on an application delimiter, the
matching delimiter highlights (occurrences highlighting); **Go to
Definition** on a delimiter goes to its match. Literal klammers match by
name (`@code``code@`) with their verbatim content opaque; everything
else matches by depth.
**Delimiter matching** — with the cursor on an application delimiter
(opening `@name`, named close `name@`, or a bare `@` close), the delimiter
and its match are boxed; a mismatched named close or an unbalanced
delimiter is boxed in **red** with a status-bar message, as in the Emacs,
Sublime, and Vim support. **Go to Definition** on a delimiter goes to its
match, so Jump to Matching Delimiter has a second home on `F12`. Literal
klammers match by name (`@code``code@`) with their verbatim content
opaque — a stray `@` in the verbatim interior cannot confuse them;
everything else matches by depth. Double-click selects a whole delimiter
token.
**Commands and keybindings** (when editing Klammertext):
**Delimiter diagnostics** — the automatic Problems-panel entries cover
all three `@`-tiers: a closing delimiter with no opening, a named close
that disagrees with its opening (`ul@` closing `@ol`), a close of the
wrong tier (`@@` closing `@name`), openings never closed, and unclosed
`@code` and `#[` regions.
| Key | Command |
|---|---|
| `Ctrl+Alt+J` (`Cmd+Alt+J`) | Klammertext: Jump to Matching Delimiter |
| `Ctrl+Alt+A` (`Cmd+Alt+A`) | Klammertext: Align Table |
**Table alignment** — pads the cells of the `@table` enclosing the cursor
so the `|` separators line up, with the rules shared across the editors:
rows end with `||`; a row with a cell over 30 characters or spanning lines
is left untouched; beyond 100 aligned columns the command declines; bars
inside a nested klammer belong to that klammer, not the table; and no
whitespace is ever inserted inside a bar run (`||` is a row separator,
`| |` an empty cell).
Table alignment pads the cells of the `@table` enclosing the cursor so the
`|` separators line up, with the shared rules: rows end with `||`; a row
with a cell over 30 characters or spanning lines is left untouched; beyond
100 columns the command declines; bars inside a nested klammer are not
separators; no whitespace is ever inserted inside a bar run.
**Text removal** — the Toggle Comment commands are VS Code's names; in
Klammertext they toggle `#` line removal and `#[ ... ]#` block removal
(the `#` does not "comment out": it removes text from processing).
**Text removal toggling**`Ctrl+/` toggles `#` line removal and
`Shift+Alt+A` wraps the selection in `#[ ... ]#`, via the standard VS Code
comment commands.
**Keybinding notes** — each Klammertext command has two bindings because
some environments never deliver `Ctrl+Alt+letter` chords to VS Code (a
right Alt is usually AltGr, not Alt, and some desktops and input methods
intercept the chord); the two-step `Ctrl+K` chords go through everywhere.
If a key seems to do nothing, run the command from the Command Palette
first: if that works, the chord is being intercepted — open **Keyboard
Shortcuts** (`Ctrl+K Ctrl+S`), search "klammertext", and rebind.
## Settings
A normal installation needs neither setting: the extension runs `python3`
from `PATH` and finds the language server automatically (the copy vendored
next to `extension.js`, then `../shared/`, then
`$KLAMMERTEXT_HOME/doc/edit/shared/`). They exist for unusual setups.
Set them in the Settings UI (`Ctrl+,`, search "klammertext") or in
`settings.json`; the server is spawned when the extension activates, so
reload the window after changing either.
| Setting | Meaning (default) |
|---|---|
| `klammertext.pythonPath` | Python interpreter for the server (`python3`) |
| `klammertext.serverPath` | full path to `klammertext_ls.py` (auto-located) |
`pythonPath` matters when `python3` is not on the `PATH` VS Code sees — a
VS Code launched from the desktop inherits a different environment than
your shell — or when a specific interpreter is wanted. `serverPath`
matters only when the server file lives outside the search chain above.
If the server cannot be started at all, the extension says so once at
activation; highlighting still works, and everything structural
(diagnostics, formatting, matching, alignment) waits until the path is
fixed.

View File

@@ -167,6 +167,11 @@ function activate(context) {
}
const python = vscode.workspace.getConfiguration('klammertext').get('pythonPath') || 'python3';
client = new LspClient(python, [serverPath], log);
client.proc.on('error', (err) => {
vscode.window.showWarningMessage(
'Klammertext: could not start the language server (' + err.message +
') — check the klammertext.pythonPath setting.');
});
const diagnostics = vscode.languages.createDiagnosticCollection('klammertext');
context.subscriptions.push(diagnostics, output);
@@ -271,6 +276,58 @@ function activate(context) {
},
}));
// -- live match/mismatch decorations --
// Drawn on every cursor move from the server's klammertext/matchInfo.
// Deliberately NOT left to occurrence highlighting: VS Code only asks
// documentHighlight providers when the cursor is on a word, so a bare @
// close would never light up — and a mismatch could not show in red.
const matchDecoration = vscode.window.createTextEditorDecorationType({
border: '1px solid',
borderColor: new vscode.ThemeColor('editorBracketMatch.border'),
backgroundColor: new vscode.ThemeColor('editorBracketMatch.background'),
});
const mismatchDecoration = vscode.window.createTextEditorDecorationType({
border: '1px solid #ff5555',
fontWeight: 'bold',
});
context.subscriptions.push(matchDecoration, mismatchDecoration);
const updateMatchDecorations = (editor) => {
if (!editor || !isKt(editor.document)) return;
client.request('klammertext/matchInfo',
Object.assign(docParams(editor.document),
{ position: fromVsPosition(editor.selection.active) }))
.then((info) => {
if (!info) {
editor.setDecorations(matchDecoration, []);
editor.setDecorations(mismatchDecoration, []);
return;
}
const ranges = [toVsRange(info.token)];
if (info.matchToken) ranges.push(toVsRange(info.matchToken));
if (info.mismatch) {
editor.setDecorations(matchDecoration, []);
editor.setDecorations(mismatchDecoration, ranges);
if (info.message) {
vscode.window.setStatusBarMessage(
'Klammertext: ' + info.message, 5000);
}
} else {
editor.setDecorations(mismatchDecoration, []);
editor.setDecorations(matchDecoration, ranges);
}
}, () => { /* server gone: leave decorations as they are */ });
};
let matchTimer = null;
context.subscriptions.push(
vscode.window.onDidChangeTextEditorSelection((event) => {
if (matchTimer) clearTimeout(matchTimer);
matchTimer = setTimeout(
() => updateMatchDecorations(event.textEditor), 50);
}),
vscode.window.onDidChangeActiveTextEditor(
(editor) => updateMatchDecorations(editor)));
// -- commands --
context.subscriptions.push(
vscode.commands.registerCommand('klammertext.jumpToMatch', () => {

View File

@@ -1,11 +1,18 @@
{
"comments": {
"lineComment": "#",
"blockComment": ["#[", "]#"]
"blockComment": [
"#[",
"]#"
]
},
"brackets": [
["#[", "]#"]
[
"#[",
"]#"
]
],
"autoClosingPairs": [],
"surroundingPairs": []
"surroundingPairs": [],
"wordPattern": "@{1,3}[A-Za-z0-9_]+|[A-Za-z0-9_]+@{1,3}|@{1,3}|[A-Za-z0-9_]+"
}

View File

@@ -2,7 +2,7 @@
"name": "klammertext",
"displayName": "Klammertext",
"description": "Klammertext language support: syntax highlighting, delimiter matching, structural reindentation, table alignment, and delimiter diagnostics.",
"version": "0.1.0",
"version": "0.1.2",
"publisher": "klammertext",
"license": "SEE LICENSE IN THE KLAMMERTEXT DISTRIBUTION",
"engines": {
@@ -53,12 +53,24 @@
}
],
"keybindings": [
{
"command": "klammertext.jumpToMatch",
"key": "ctrl+k j",
"mac": "cmd+k j",
"when": "editorTextFocus && editorLangId == klammertext"
},
{
"command": "klammertext.jumpToMatch",
"key": "ctrl+alt+j",
"mac": "cmd+alt+j",
"when": "editorTextFocus && editorLangId == klammertext"
},
{
"command": "klammertext.alignTable",
"key": "ctrl+k a",
"mac": "cmd+k a",
"when": "editorTextFocus && editorLangId == klammertext"
},
{
"command": "klammertext.alignTable",
"key": "ctrl+alt+a",
@@ -80,6 +92,20 @@
"description": "Full path to klammertext_ls.py. Leave blank to auto-locate: a copy next to the extension, ../shared/ relative to it (the Klammertext repository layout), or $KLAMMERTEXT_HOME/doc/edit/shared/."
}
}
},
"menus": {
"editor/context": [
{
"command": "klammertext.jumpToMatch",
"when": "editorLangId == klammertext",
"group": "1_modification@10"
},
{
"command": "klammertext.alignTable",
"when": "editorLangId == klammertext",
"group": "1_modification@11"
}
]
}
}
}

View File

@@ -114,12 +114,13 @@ primitive klammers (`@read`, `@eval`, `@cond`), use `-k none`.
## Editor support (Emacs, Sublime Text)
Editing Klammertext is nicer with editor support: syntax highlighting,
delimiter matching, and indentation for Emacs and Sublime Text. It is not
delimiter matching, indentation, table alignment, and diagnostics for
Emacs, Sublime Text, Vim, and Visual Studio Code. It is not
inside the container image — it belongs on your machine, next to your editor.
Download it from either place:
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/` and
`sublime/` folders
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/`,
`sublime/`, `vim/`, and `vscode/` folders, each self-contained
- the Klammertext source repository,
<https://git.andykopra.com/ack/klammertext>, directory `doc/edit/`

View File

@@ -148,12 +148,13 @@ That's it — you're running Klammertext.
## Editor support (Emacs, Sublime Text)
Editing Klammertext is nicer with editor support: syntax highlighting,
delimiter matching, and indentation for Emacs and Sublime Text. It is not
delimiter matching, indentation, table alignment, and diagnostics for
Emacs, Sublime Text, Vim, and Visual Studio Code. It is not
inside the container image — it belongs on your Mac, next to your editor.
Download it from either place:
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/` and
`sublime/` folders
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/`,
`sublime/`, `vim/`, and `vscode/` folders, each self-contained
- the Klammertext source repository,
<https://git.andykopra.com/ack/klammertext>, directory `doc/edit/`