Editor support generalized: shared core, language server, Vim and VS Code (from dev eb5baf9cbe59)
doc/edit/ now holds a shared Python implementation of the language's structural layer (klammertext_edit.py) and a dependency-free language server (klammertext_ls.py), with integrations for Emacs, Sublime Text, Vim, and Visual Studio Code. The editor test suite in tst/ covers the core's API and CLI, the language server protocol, the VS Code extension, headless Vim, and Emacs byte-equality. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
100
doc/edit/vscode/README.md
Normal file
100
doc/edit/vscode/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Klammertext support for Visual Studio Code
|
||||
|
||||
VS Code support for editing Klammertext files (`.kt` documents and `.k`
|
||||
klammer definitions): syntax highlighting, delimiter matching and jumping,
|
||||
structural reindentation, table alignment, and delimiter diagnostics in the
|
||||
Problems panel.
|
||||
|
||||
The extension has **no npm dependencies and no build step**. Highlighting
|
||||
is a TextMate grammar (converted from the Sublime Text syntax); everything
|
||||
structural comes from the **Klammertext language server**
|
||||
(`klammertext_ls.py`), a dependency-free Python process the extension
|
||||
spawns, which itself runs the shared editor core (`klammertext_edit.py`)
|
||||
used by the Sublime Text and Vim integrations. One implementation of the
|
||||
language's structure, everywhere.
|
||||
|
||||
## Requirements
|
||||
|
||||
- VS Code 1.75 or later — a minimum, not a target: VS Code's monthly
|
||||
releases count 1.75, 1.76, … (1.75 is from January 2023), so any
|
||||
version from the last few years qualifies.
|
||||
- `python3` on `PATH` (or set `klammertext.pythonPath`); Klammertext itself
|
||||
already requires Python.
|
||||
- The language server, found automatically in this order:
|
||||
1. the `klammertext.serverPath` setting, if set;
|
||||
2. `klammertext_ls.py` vendored next to `extension.js` (the layout the
|
||||
Klammertext editing zip ships);
|
||||
3. `../shared/klammertext_ls.py` relative to the extension directory (the
|
||||
layout of the Klammertext repository — using the extension straight
|
||||
from a checkout just works);
|
||||
4. `$KLAMMERTEXT_HOME/doc/edit/shared/klammertext_ls.py`.
|
||||
|
||||
## Install
|
||||
|
||||
Copy this `vscode/` directory into your VS Code extensions folder:
|
||||
|
||||
cp -R vscode ~/.vscode/extensions/klammertext
|
||||
|
||||
then restart VS Code (or run the **Developer: Reload Window** command). If
|
||||
you copy the directory out of the Klammertext tree, also copy
|
||||
`shared/klammertext_ls.py` and `shared/klammertext_edit.py` into the copied
|
||||
folder — or set `klammertext.serverPath`. The editing zip from the
|
||||
Klammertext website ships the vendored copies already in place.
|
||||
|
||||
**Note:** `.kt` is also Kotlin's extension. Stock VS Code has no Kotlin
|
||||
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
|
||||
|
||||
**Syntax highlighting** — the same token classes as the Emacs, Sublime
|
||||
Text, and Vim support: text removal (`#`, `##`, nestable `#[ ... ]#`), the
|
||||
three `@`-tiers — application (`@`), definition (`@@`), system (`@@@`) —
|
||||
each as an opening (`@name`, one unit) or a close (`name@`, bare `@`),
|
||||
`^`-escapes, and verbatim `@code ... code@` interiors. Colors come from
|
||||
your theme (applications as functions, definitions as types, system
|
||||
commands as keywords, removed text as comments). To adopt the full
|
||||
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
|
||||
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.
|
||||
|
||||
**Commands and keybindings** (when editing Klammertext):
|
||||
|
||||
| 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 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 toggling** — `Ctrl+/` toggles `#` line removal and
|
||||
`Shift+Alt+A` wraps the selection in `#[ ... ]#`, via the standard VS Code
|
||||
comment commands.
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Meaning (default) |
|
||||
|---|---|
|
||||
| `klammertext.pythonPath` | Python interpreter for the server (`python3`) |
|
||||
| `klammertext.serverPath` | full path to `klammertext_ls.py` (auto-located) |
|
||||
309
doc/edit/vscode/extension.js
Normal file
309
doc/edit/vscode/extension.js
Normal file
@@ -0,0 +1,309 @@
|
||||
// extension.js — the Klammertext VS Code extension.
|
||||
//
|
||||
// Everything structural (diagnostics, reindentation, table alignment,
|
||||
// delimiter matching) comes from the Klammertext language server
|
||||
// (klammertext_ls.py), which itself runs the shared editor core used by the
|
||||
// Sublime Text and Vim integrations. This file is glue: it spawns the
|
||||
// server and speaks the Language Server Protocol to it directly — the
|
||||
// framing and dispatch below are small, so the extension has NO npm
|
||||
// dependencies and no build step (deliberately, matching Klammertext's
|
||||
// no-third-party-libraries ethos).
|
||||
//
|
||||
// What the extension wires up:
|
||||
// * document sync (full text) for klammertext documents
|
||||
// * publishDiagnostics -> the Problems panel
|
||||
// * Format Document / Format Selection -> textDocument/(range)formatting
|
||||
// (structural reindentation; explicit-only — no format-on-type)
|
||||
// * occurrences highlighting -> textDocument/documentHighlight (the
|
||||
// matching delimiter lights up as the cursor sits on one)
|
||||
// * Go to Definition on a delimiter -> its matching delimiter
|
||||
// * klammertext.jumpToMatch (Ctrl+Alt+J) -> move the cursor to the match
|
||||
// * klammertext.alignTable (Ctrl+Alt+A) -> workspace/executeCommand; the
|
||||
// server answers with workspace/applyEdit
|
||||
//
|
||||
// The server is located via the klammertext.serverPath setting, a vendored
|
||||
// copy next to this file (the editing-zip layout), ../shared/ relative to
|
||||
// it (the Klammertext repository layout), or $KLAMMERTEXT_HOME.
|
||||
|
||||
'use strict';
|
||||
|
||||
const vscode = require('vscode');
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// --- a minimal LSP client over a child process -----------------------------
|
||||
|
||||
class LspClient {
|
||||
constructor(command, args, log) {
|
||||
this.log = log;
|
||||
this.nextId = 1;
|
||||
this.pending = new Map(); // id -> {resolve, reject}
|
||||
this.handlers = new Map(); // method -> fn(params, id)
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.dead = false;
|
||||
this.proc = cp.spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
this.proc.stdout.on('data', (chunk) => this._onData(chunk));
|
||||
this.proc.stderr.on('data', (chunk) => log(chunk.toString()));
|
||||
this.proc.on('error', (err) => { this.dead = true; log('spawn error: ' + err.message); });
|
||||
this.proc.on('exit', (code) => { this.dead = true; log('server exited: ' + code); });
|
||||
}
|
||||
|
||||
_onData(chunk) {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
for (;;) {
|
||||
const sep = this.buffer.indexOf('\r\n\r\n');
|
||||
if (sep === -1) return;
|
||||
const header = this.buffer.slice(0, sep).toString();
|
||||
const m = /content-length:\s*(\d+)/i.exec(header);
|
||||
if (!m) { this.buffer = this.buffer.slice(sep + 4); continue; }
|
||||
const length = parseInt(m[1], 10);
|
||||
if (this.buffer.length < sep + 4 + length) return;
|
||||
const body = this.buffer.slice(sep + 4, sep + 4 + length).toString();
|
||||
this.buffer = this.buffer.slice(sep + 4 + length);
|
||||
let msg;
|
||||
try { msg = JSON.parse(body); } catch (e) { continue; }
|
||||
this._dispatch(msg);
|
||||
}
|
||||
}
|
||||
|
||||
_dispatch(msg) {
|
||||
if (msg.method !== undefined) {
|
||||
const handler = this.handlers.get(msg.method);
|
||||
if (handler) {
|
||||
Promise.resolve(handler(msg.params, msg.id)).then((result) => {
|
||||
if (msg.id !== undefined && msg.id !== null) {
|
||||
this._send({ jsonrpc: '2.0', id: msg.id, result: result === undefined ? null : result });
|
||||
}
|
||||
});
|
||||
} else if (msg.id !== undefined && msg.id !== null) {
|
||||
this._send({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: 'method not found' } });
|
||||
}
|
||||
} else if (this.pending.has(msg.id)) {
|
||||
const p = this.pending.get(msg.id);
|
||||
this.pending.delete(msg.id);
|
||||
if (msg.error) p.reject(new Error(msg.error.message));
|
||||
else p.resolve(msg.result);
|
||||
}
|
||||
}
|
||||
|
||||
_send(msg) {
|
||||
if (this.dead) return;
|
||||
const body = Buffer.from(JSON.stringify(msg), 'utf8');
|
||||
this.proc.stdin.write('Content-Length: ' + body.length + '\r\n\r\n');
|
||||
this.proc.stdin.write(body);
|
||||
}
|
||||
|
||||
request(method, params) {
|
||||
if (this.dead) return Promise.reject(new Error('server not running'));
|
||||
const id = this.nextId++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.set(id, { resolve, reject });
|
||||
this._send({ jsonrpc: '2.0', id, method, params });
|
||||
});
|
||||
}
|
||||
|
||||
notify(method, params) {
|
||||
this._send({ jsonrpc: '2.0', method, params });
|
||||
}
|
||||
|
||||
onRequest(method, handler) { this.handlers.set(method, handler); }
|
||||
|
||||
stop() {
|
||||
if (this.dead) return;
|
||||
this.request('shutdown', null).then(
|
||||
() => { this.notify('exit', null); },
|
||||
() => { try { this.proc.kill(); } catch (e) { /* gone */ } });
|
||||
}
|
||||
}
|
||||
|
||||
// --- LSP <-> VS Code conversions -------------------------------------------
|
||||
|
||||
function toVsRange(r) {
|
||||
return new vscode.Range(r.start.line, r.start.character, r.end.line, r.end.character);
|
||||
}
|
||||
|
||||
function toVsEdits(edits) {
|
||||
return (edits || []).map((e) => new vscode.TextEdit(toVsRange(e.range), e.newText));
|
||||
}
|
||||
|
||||
function fromVsPosition(p) {
|
||||
return { line: p.line, character: p.character };
|
||||
}
|
||||
|
||||
function docParams(document) {
|
||||
return { textDocument: { uri: document.uri.toString() } };
|
||||
}
|
||||
|
||||
// --- locating the server ---------------------------------------------------
|
||||
|
||||
function findServer(context) {
|
||||
const configured = vscode.workspace.getConfiguration('klammertext').get('serverPath');
|
||||
const candidates = [];
|
||||
if (configured) candidates.push(configured);
|
||||
candidates.push(path.join(context.extensionPath, 'klammertext_ls.py'));
|
||||
candidates.push(path.join(context.extensionPath, '..', 'shared', 'klammertext_ls.py'));
|
||||
if (process.env.KLAMMERTEXT_HOME) {
|
||||
candidates.push(path.join(process.env.KLAMMERTEXT_HOME, 'doc', 'edit', 'shared', 'klammertext_ls.py'));
|
||||
}
|
||||
return candidates.find((c) => { try { return fs.statSync(c).isFile(); } catch (e) { return false; } });
|
||||
}
|
||||
|
||||
// --- activation ------------------------------------------------------------
|
||||
|
||||
let client = null;
|
||||
|
||||
function activate(context) {
|
||||
const output = vscode.window.createOutputChannel('Klammertext');
|
||||
const log = (s) => output.append(s.endsWith('\n') ? s : s + '\n');
|
||||
|
||||
const serverPath = findServer(context);
|
||||
if (!serverPath) {
|
||||
vscode.window.showWarningMessage(
|
||||
'Klammertext: cannot locate klammertext_ls.py — set the ' +
|
||||
'klammertext.serverPath setting. Highlighting works; ' +
|
||||
'diagnostics, formatting, matching and alignment need the server.');
|
||||
return;
|
||||
}
|
||||
const python = vscode.workspace.getConfiguration('klammertext').get('pythonPath') || 'python3';
|
||||
client = new LspClient(python, [serverPath], log);
|
||||
|
||||
const diagnostics = vscode.languages.createDiagnosticCollection('klammertext');
|
||||
context.subscriptions.push(diagnostics, output);
|
||||
|
||||
client.onRequest('textDocument/publishDiagnostics', (params) => {
|
||||
diagnostics.set(vscode.Uri.parse(params.uri), (params.diagnostics || []).map((d) => {
|
||||
const diag = new vscode.Diagnostic(
|
||||
toVsRange(d.range), d.message,
|
||||
d.severity === 1 ? vscode.DiagnosticSeverity.Error
|
||||
: vscode.DiagnosticSeverity.Warning);
|
||||
diag.source = d.source;
|
||||
return diag;
|
||||
}));
|
||||
});
|
||||
|
||||
client.onRequest('workspace/applyEdit', (params) => {
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
const changes = (params.edit && params.edit.changes) || {};
|
||||
for (const uri of Object.keys(changes)) {
|
||||
for (const e of changes[uri]) {
|
||||
we.replace(vscode.Uri.parse(uri), toVsRange(e.range), e.newText);
|
||||
}
|
||||
}
|
||||
return vscode.workspace.applyEdit(we).then((applied) => ({ applied }));
|
||||
});
|
||||
|
||||
client.onRequest('window/showMessage', (params) => {
|
||||
vscode.window.setStatusBarMessage(params.message, 5000);
|
||||
});
|
||||
|
||||
// -- document sync (full text) --
|
||||
const isKt = (doc) => doc.languageId === 'klammertext';
|
||||
const open = (doc) => {
|
||||
if (!isKt(doc)) return;
|
||||
client.notify('textDocument/didOpen', {
|
||||
textDocument: { uri: doc.uri.toString(), languageId: 'klammertext',
|
||||
version: doc.version, text: doc.getText() },
|
||||
});
|
||||
};
|
||||
|
||||
client.request('initialize', {
|
||||
processId: process.pid,
|
||||
rootUri: null,
|
||||
capabilities: {},
|
||||
}).then(() => {
|
||||
client.notify('initialized', {});
|
||||
vscode.workspace.textDocuments.forEach(open);
|
||||
}, (err) => log('initialize failed: ' + err.message));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument(open),
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
if (!isKt(event.document)) return;
|
||||
client.notify('textDocument/didChange', {
|
||||
textDocument: { uri: event.document.uri.toString(),
|
||||
version: event.document.version },
|
||||
contentChanges: [{ text: event.document.getText() }],
|
||||
});
|
||||
}),
|
||||
vscode.workspace.onDidCloseTextDocument((doc) => {
|
||||
if (!isKt(doc)) return;
|
||||
client.notify('textDocument/didClose', docParams(doc));
|
||||
}));
|
||||
|
||||
// -- providers --
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerDocumentFormattingEditProvider('klammertext', {
|
||||
provideDocumentFormattingEdits(document) {
|
||||
return client.request('textDocument/formatting',
|
||||
Object.assign(docParams(document), { options: {} }))
|
||||
.then(toVsEdits);
|
||||
},
|
||||
}),
|
||||
vscode.languages.registerDocumentRangeFormattingEditProvider('klammertext', {
|
||||
provideDocumentRangeFormattingEdits(document, range) {
|
||||
return client.request('textDocument/rangeFormatting',
|
||||
Object.assign(docParams(document), {
|
||||
range: { start: fromVsPosition(range.start),
|
||||
end: fromVsPosition(range.end) },
|
||||
options: {},
|
||||
})).then(toVsEdits);
|
||||
},
|
||||
}),
|
||||
vscode.languages.registerDocumentHighlightProvider('klammertext', {
|
||||
provideDocumentHighlights(document, position) {
|
||||
return client.request('textDocument/documentHighlight',
|
||||
Object.assign(docParams(document),
|
||||
{ position: fromVsPosition(position) }))
|
||||
.then((result) => (result || []).map((h) =>
|
||||
new vscode.DocumentHighlight(toVsRange(h.range))));
|
||||
},
|
||||
}),
|
||||
vscode.languages.registerDefinitionProvider('klammertext', {
|
||||
provideDefinition(document, position) {
|
||||
return client.request('textDocument/definition',
|
||||
Object.assign(docParams(document),
|
||||
{ position: fromVsPosition(position) }))
|
||||
.then((result) => result
|
||||
? new vscode.Location(vscode.Uri.parse(result.uri),
|
||||
toVsRange(result.range))
|
||||
: null);
|
||||
},
|
||||
}));
|
||||
|
||||
// -- commands --
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('klammertext.jumpToMatch', () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor || !isKt(editor.document)) return;
|
||||
return client.request('textDocument/definition',
|
||||
Object.assign(docParams(editor.document),
|
||||
{ position: fromVsPosition(editor.selection.active) }))
|
||||
.then((result) => {
|
||||
if (!result) {
|
||||
vscode.window.setStatusBarMessage(
|
||||
'Klammertext: no matching delimiter here', 5000);
|
||||
return;
|
||||
}
|
||||
const pos = toVsRange(result.range).start;
|
||||
editor.selection = new vscode.Selection(pos, pos);
|
||||
editor.revealRange(new vscode.Range(pos, pos));
|
||||
});
|
||||
}),
|
||||
vscode.commands.registerCommand('klammertext.alignTable', () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor || !isKt(editor.document)) return;
|
||||
return client.request('workspace/executeCommand', {
|
||||
command: 'klammertext.alignTable',
|
||||
arguments: [{ uri: editor.document.uri.toString(),
|
||||
position: fromVsPosition(editor.selection.active) }],
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
if (client) client.stop();
|
||||
client = null;
|
||||
}
|
||||
|
||||
module.exports = { activate, deactivate };
|
||||
11
doc/edit/vscode/language-configuration.json
Normal file
11
doc/edit/vscode/language-configuration.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"comments": {
|
||||
"lineComment": "#",
|
||||
"blockComment": ["#[", "]#"]
|
||||
},
|
||||
"brackets": [
|
||||
["#[", "]#"]
|
||||
],
|
||||
"autoClosingPairs": [],
|
||||
"surroundingPairs": []
|
||||
}
|
||||
85
doc/edit/vscode/package.json
Normal file
85
doc/edit/vscode/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "klammertext",
|
||||
"displayName": "Klammertext",
|
||||
"description": "Klammertext language support: syntax highlighting, delimiter matching, structural reindentation, table alignment, and delimiter diagnostics.",
|
||||
"version": "0.1.0",
|
||||
"publisher": "klammertext",
|
||||
"license": "SEE LICENSE IN THE KLAMMERTEXT DISTRIBUTION",
|
||||
"engines": {
|
||||
"vscode": "^1.75.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
],
|
||||
"main": "./extension.js",
|
||||
"activationEvents": [
|
||||
"onLanguage:klammertext"
|
||||
],
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
"supported": false,
|
||||
"description": "The extension runs the Klammertext language server (a local Python process)."
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "klammertext",
|
||||
"aliases": [
|
||||
"Klammertext"
|
||||
],
|
||||
"extensions": [
|
||||
".kt",
|
||||
".k"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "klammertext",
|
||||
"scopeName": "text.klammertext",
|
||||
"path": "./syntaxes/klammertext.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "klammertext.jumpToMatch",
|
||||
"title": "Klammertext: Jump to Matching Delimiter"
|
||||
},
|
||||
{
|
||||
"command": "klammertext.alignTable",
|
||||
"title": "Klammertext: Align Table"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "klammertext.jumpToMatch",
|
||||
"key": "ctrl+alt+j",
|
||||
"mac": "cmd+alt+j",
|
||||
"when": "editorTextFocus && editorLangId == klammertext"
|
||||
},
|
||||
{
|
||||
"command": "klammertext.alignTable",
|
||||
"key": "ctrl+alt+a",
|
||||
"mac": "cmd+alt+a",
|
||||
"when": "editorTextFocus && editorLangId == klammertext"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Klammertext",
|
||||
"properties": {
|
||||
"klammertext.pythonPath": {
|
||||
"type": "string",
|
||||
"default": "python3",
|
||||
"description": "Python interpreter used to run the Klammertext language server."
|
||||
},
|
||||
"klammertext.serverPath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"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/."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
doc/edit/vscode/syntaxes/klammertext.tmLanguage.json
Normal file
132
doc/edit/vscode/syntaxes/klammertext.tmLanguage.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"//": [
|
||||
"TextMate grammar for Klammertext — the VS Code port of",
|
||||
"doc/edit/sublime/Klammertext.sublime-syntax (same token classes,",
|
||||
"same scope names; see that file's header for the full rationale).",
|
||||
"",
|
||||
"What it highlights: text removal (#, ##, nestable #[ ... ]#,",
|
||||
"whitespace operators left unscoped), the three @-tiers — application",
|
||||
"(@), definition (@@), system (@@@) — each as an opening (@name, one",
|
||||
"unit) or a close (name@, bare @), ^-escapes (consumed, unscoped),",
|
||||
"and verbatim @code ... code@ interiors.",
|
||||
"",
|
||||
"How open vs. close is decided (the same rule as every integration):",
|
||||
"a delimiter whose NAME follows the @-run is an OPENING; a bare",
|
||||
"@-run, or one whose NAME precedes it, is a CLOSING. The",
|
||||
"(?![A-Za-z0-9_@]) look-ahead on every closing keeps foo@bar correct.",
|
||||
"",
|
||||
"SYNC: the literal-klammer set's source of truth is LITERAL_KLAMMERS",
|
||||
"in doc/edit/shared/klammertext_edit.py. A static grammar cannot",
|
||||
"read it: to add a literal klammer 'foo', copy the @code begin/end",
|
||||
"rule below with code -> foo (and mirror it in the Emacs, Sublime,",
|
||||
"and Vim artifacts; all are seeded with just 'code').",
|
||||
"",
|
||||
"Delimiter matching, indentation, alignment, and diagnostics are not",
|
||||
"tokenizer concerns — they come from the Klammertext language server",
|
||||
"via extension.js.",
|
||||
"",
|
||||
"The ## rule's end pattern never matches, so the region runs to the",
|
||||
"end of the file (## removes the rest of the file by definition)."
|
||||
],
|
||||
"name": "Klammertext",
|
||||
"scopeName": "text.klammertext",
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\^."
|
||||
},
|
||||
{
|
||||
"begin": "#\\[",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"end": "\\]#",
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"name": "comment.block.klammertext",
|
||||
"patterns": [
|
||||
{ "include": "#removal-block" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "#[-+/]\\d*"
|
||||
},
|
||||
{
|
||||
"begin": "##",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"end": "$never^",
|
||||
"name": "comment.block.klammertext"
|
||||
},
|
||||
{
|
||||
"begin": "#",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"end": "$",
|
||||
"name": "comment.line.klammertext"
|
||||
},
|
||||
{
|
||||
"begin": "@code(?![A-Za-z0-9_])",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "entity.name.function.begin.klammertext" }
|
||||
},
|
||||
"end": "code@",
|
||||
"endCaptures": {
|
||||
"0": { "name": "entity.name.function.end.klammertext" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": "@@@[A-Za-z0-9_]+",
|
||||
"name": "keyword.control.begin.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "@@@(?![A-Za-z0-9_@])",
|
||||
"name": "keyword.control.end.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "[A-Za-z0-9_]+@@@(?![A-Za-z0-9_@])",
|
||||
"name": "keyword.control.end.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "@@[A-Za-z0-9_]+",
|
||||
"name": "storage.type.begin.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "@@(?![A-Za-z0-9_@])",
|
||||
"name": "storage.type.end.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "[A-Za-z0-9_]+@@(?![A-Za-z0-9_@])",
|
||||
"name": "storage.type.end.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "@[A-Za-z0-9_]+",
|
||||
"name": "entity.name.function.begin.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "@(?![A-Za-z0-9_@])",
|
||||
"name": "entity.name.function.end.klammertext"
|
||||
},
|
||||
{
|
||||
"match": "[A-Za-z0-9_]+@(?![A-Za-z0-9_@])",
|
||||
"name": "entity.name.function.end.klammertext"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"removal-block": {
|
||||
"begin": "#\\[",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"end": "\\]#",
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.comment.klammertext" }
|
||||
},
|
||||
"patterns": [
|
||||
{ "include": "#removal-block" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user