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:
@@ -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, '')
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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_]+"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user