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:
117
doc/edit/vim/README.md
Normal file
117
doc/edit/vim/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Klammertext support for Vim
|
||||
|
||||
Vim support for editing Klammertext files (`.kt` documents and `.k` klammer
|
||||
definitions): syntax highlighting, delimiter matching and jumping, structural
|
||||
reindentation, table alignment, and a delimiter checker.
|
||||
|
||||
The structure-aware features are thin wrappers around the **shared editor
|
||||
core** (`klammertext_edit.py`) — the single Python implementation of
|
||||
Klammertext's structural layer used by the Sublime Text and VS Code
|
||||
integrations and by the Klammertext language server. The Vim plugin runs it
|
||||
through `python3`; there is no Vim-specific reimplementation to drift out of
|
||||
sync.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Vim 8+ (or Neovim) with `+eval` — any normal Vim; only `vim-tiny` lacks it.
|
||||
- `python3` on `PATH` (Klammertext itself already requires Python).
|
||||
- The shared core, found automatically in this order:
|
||||
1. `g:klammertext_edit_py`, if you set it (a full path to
|
||||
`klammertext_edit.py`);
|
||||
2. a `klammertext_edit.py` vendored at this plugin's root (the layout the
|
||||
Klammertext editing zip ships);
|
||||
3. `../shared/klammertext_edit.py` relative to the plugin directory (the
|
||||
layout of the Klammertext repository — using the plugin straight from a
|
||||
checkout just works);
|
||||
4. `$KLAMMERTEXT_HOME/doc/edit/shared/klammertext_edit.py`.
|
||||
|
||||
## Install
|
||||
|
||||
Copy (or symlink) this `vim/` directory into a Vim native package path,
|
||||
renaming it as you like:
|
||||
|
||||
mkdir -p ~/.vim/pack/klammertext/start
|
||||
cp -R vim ~/.vim/pack/klammertext/start/klammertext
|
||||
|
||||
(Neovim: `~/.local/share/nvim/site/pack/klammertext/start/klammertext`.)
|
||||
If you copy the directory out of the Klammertext tree, also copy
|
||||
`shared/klammertext_edit.py` into the copied folder's root — or set
|
||||
`g:klammertext_edit_py`. The editing zip from the Klammertext website ships
|
||||
the vendored copy already in place.
|
||||
|
||||
`.kt` and `.k` files then get the `klammertext` filetype. **Note:** `.kt`
|
||||
is also Kotlin's extension, and stock Vim maps it to Kotlin; this plugin's
|
||||
`ftdetect` overrides that unconditionally. If you edit both languages, see
|
||||
the comment in `ftdetect/klammertext.vim`.
|
||||
|
||||
## What you get
|
||||
|
||||
**Syntax highlighting** — the same token classes and palette as the Emacs
|
||||
and Sublime Text support: text removal (`#`, `##`, nestable `#[ ... ]#`),
|
||||
the three `@`-tiers — application (`@`, blue), definition (`@@`, green),
|
||||
system (`@@@`, orange) — each as an opening (`@name`) or a close (`name@`,
|
||||
bare `@`), opens bright and closes the same hue darker; `^`-escapes;
|
||||
verbatim `@code ... code@` interiors. All groups are `hi def` — override
|
||||
them with `:highlight` in your vimrc.
|
||||
|
||||
**Commands** (buffer-local; default mappings below):
|
||||
|
||||
| Command | Does |
|
||||
|---|---|
|
||||
| `:[range]KlammertextReindent` | reindent the range (default: whole buffer) |
|
||||
| `:KlammertextAlign` | align the `@table` enclosing the cursor |
|
||||
| `:KlammertextJumpToMatch` | jump between a klammer application's opening and closing delimiter |
|
||||
| `:KlammertextCheck` | list unclosed/mismatched delimiters in the location list |
|
||||
|
||||
Default mappings (buffer-local; suppress them all with
|
||||
`let g:klammertext_no_mappings = 1`): `<LocalLeader>i` reindents the current
|
||||
line (in visual mode, the selection), `<LocalLeader>a` aligns the enclosing
|
||||
table, `<LocalLeader>j` jumps to the matching delimiter. `LocalLeader`
|
||||
defaults to backslash; set `maplocalleader` to taste.
|
||||
|
||||
Reindentation is **explicit-only**: whitespace is content in Klammertext,
|
||||
so nothing reformats as a side effect of typing (`indentkeys` is emptied).
|
||||
Alignment follows 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.
|
||||
|
||||
**With `+python3`** (check `:echo has('python3')`) the shared core also runs
|
||||
in-process: the `=` operator reindents through `'indentexpr'` (`==`, `gg=G`),
|
||||
and the matching delimiter is highlighted live as the cursor sits on one —
|
||||
the show-paren equivalent, with a mismatched or unbalanced delimiter shown
|
||||
in red plus a message. Without `+python3` the commands above still work;
|
||||
they shell out to `python3`.
|
||||
|
||||
**Comment toggling** — `'commentstring'` is set to `# %s`, so Vim 9.1's
|
||||
built-in commenting and Neovim's `gcc`/`gc` (or the commentary plugin)
|
||||
toggle `#` line removal.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Meaning (default) |
|
||||
|---|---|
|
||||
| `g:klammertext_edit_py` | full path to `klammertext_edit.py` (auto-detected) |
|
||||
| `g:klammertext_python` | Python interpreter (`python3`) |
|
||||
| `g:klammertext_no_mappings` | set to define no default mappings |
|
||||
|
||||
## Neovim and the language server
|
||||
|
||||
Neovim users can additionally attach Neovim's built-in LSP client to the
|
||||
Klammertext language server (`klammertext_ls.py`, next to the shared core)
|
||||
for diagnostics as you type, `gq`/format-document reindentation, and
|
||||
cursor-hold match highlighting:
|
||||
|
||||
```lua
|
||||
vim.api.nvim_create_autocmd('FileType', {
|
||||
pattern = 'klammertext',
|
||||
callback = function()
|
||||
vim.lsp.start {
|
||||
name = 'klammertext-ls',
|
||||
cmd = { 'python3', '/path/to/doc/edit/shared/klammertext_ls.py' },
|
||||
}
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
The plugin's own commands work the same with or without it.
|
||||
299
doc/edit/vim/autoload/klammertext.vim
Normal file
299
doc/edit/vim/autoload/klammertext.vim
Normal file
@@ -0,0 +1,299 @@
|
||||
" autoload/klammertext.vim — implementation of the Klammertext commands.
|
||||
"
|
||||
" All the structure-aware work (indentation, table alignment, delimiter
|
||||
" matching, diagnostics) is done by the shared Python core,
|
||||
" klammertext_edit.py — the single implementation used by the Sublime,
|
||||
" Vim, and VS Code integrations and by the language server. This file is
|
||||
" glue: it locates the core, runs it (as a stdin/stdout filter, or
|
||||
" in-process via +python3 where that is faster), and applies the results
|
||||
" to the buffer.
|
||||
"
|
||||
" Configuration:
|
||||
" g:klammertext_edit_py full path to klammertext_edit.py (overrides the
|
||||
" search described below)
|
||||
" g:klammertext_python the Python interpreter (default "python3")
|
||||
|
||||
" --- locating the shared core ---------------------------------------------
|
||||
" Search order: an explicit g:klammertext_edit_py; a vendored copy at this
|
||||
" plugin's root (the layout the editing zip installs); ../shared relative
|
||||
" to the plugin root (the repository layout); $KLAMMERTEXT_HOME.
|
||||
|
||||
let s:plugin_root = expand('<sfile>:p:h:h')
|
||||
|
||||
function! s:EditScript() abort
|
||||
if exists('g:klammertext_edit_py')
|
||||
return g:klammertext_edit_py
|
||||
endif
|
||||
for candidate in [
|
||||
\ s:plugin_root . '/klammertext_edit.py',
|
||||
\ fnamemodify(s:plugin_root, ':h') . '/shared/klammertext_edit.py',
|
||||
\ (empty($KLAMMERTEXT_HOME) ? '' :
|
||||
\ $KLAMMERTEXT_HOME . '/doc/edit/shared/klammertext_edit.py')]
|
||||
if !empty(candidate) && filereadable(candidate)
|
||||
return candidate
|
||||
endif
|
||||
endfor
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! s:Python() abort
|
||||
return exists('g:klammertext_python') ? g:klammertext_python : 'python3'
|
||||
endfunction
|
||||
|
||||
" Run the core CLI over the whole buffer. ARGS is the argument string
|
||||
" (e.g. "indent 3-7"). Returns a dict {ok, lines, msg}: LINES is the
|
||||
" transformed buffer (for the filter modes), MSG the stderr message.
|
||||
function! s:RunCore(args) abort
|
||||
let script = s:EditScript()
|
||||
if empty(script)
|
||||
return {'ok': 0, 'lines': [], 'msg':
|
||||
\ 'Klammertext: cannot locate klammertext_edit.py'
|
||||
\ . ' (set g:klammertext_edit_py)'}
|
||||
endif
|
||||
let errfile = tempname()
|
||||
let cmd = s:Python() . ' ' . shellescape(script) . ' ' . a:args
|
||||
\ . ' 2>' . shellescape(errfile)
|
||||
let out = systemlist(cmd, getline(1, '$'))
|
||||
let msg = filereadable(errfile) ? join(readfile(errfile), ' ') : ''
|
||||
call delete(errfile)
|
||||
if v:shell_error
|
||||
return {'ok': 0, 'lines': [], 'msg':
|
||||
\ empty(msg) ? 'Klammertext: the shared core failed' : msg}
|
||||
endif
|
||||
return {'ok': 1, 'lines': out, 'msg': msg}
|
||||
endfunction
|
||||
|
||||
" Replace the buffer with LINES, touching only the lines that changed (so
|
||||
" undo stays small and the cursor does not move). The transformations
|
||||
" never add or remove lines; refuse if the count disagrees.
|
||||
function! s:ApplyLines(lines) abort
|
||||
if len(a:lines) != line('$')
|
||||
echohl ErrorMsg
|
||||
echo 'Klammertext: unexpected line count from the shared core'
|
||||
echohl None
|
||||
return 0
|
||||
endif
|
||||
let changed = 0
|
||||
for i in range(1, line('$'))
|
||||
if getline(i) !=# a:lines[i - 1]
|
||||
call setline(i, a:lines[i - 1])
|
||||
let changed += 1
|
||||
endif
|
||||
endfor
|
||||
return changed
|
||||
endfunction
|
||||
|
||||
function! s:CursorArgs() abort
|
||||
let col = exists('*charcol') ? charcol('.') : col('.')
|
||||
return line('.') . ' ' . col
|
||||
endfunction
|
||||
|
||||
" --- the commands ----------------------------------------------------------
|
||||
|
||||
function! klammertext#Reindent(first, last) abort
|
||||
let r = s:RunCore('indent ' . a:first . '-' . a:last)
|
||||
if !r.ok
|
||||
echohl ErrorMsg | echo r.msg | echohl None
|
||||
return
|
||||
endif
|
||||
call s:ApplyLines(r.lines)
|
||||
endfunction
|
||||
|
||||
function! klammertext#AlignTable() abort
|
||||
let r = s:RunCore('align ' . s:CursorArgs())
|
||||
if !r.ok
|
||||
echohl ErrorMsg | echo r.msg | echohl None
|
||||
return
|
||||
endif
|
||||
call s:ApplyLines(r.lines)
|
||||
if !empty(r.msg)
|
||||
echo 'Klammertext: ' . r.msg
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! klammertext#JumpToMatch() abort
|
||||
let script = s:EditScript()
|
||||
if empty(script)
|
||||
echohl ErrorMsg
|
||||
echo 'Klammertext: cannot locate klammertext_edit.py'
|
||||
echohl None
|
||||
return
|
||||
endif
|
||||
let out = systemlist(s:Python() . ' ' . shellescape(script)
|
||||
\ . ' match ' . s:CursorArgs(), getline(1, '$'))
|
||||
if v:shell_error || empty(out)
|
||||
echohl ErrorMsg | echo 'Klammertext: the shared core failed' | echohl None
|
||||
return
|
||||
endif
|
||||
let parts = split(out[0], ' ')
|
||||
if parts[0] ==# 'none'
|
||||
echo 'Klammertext: ' . join(parts[1:], ' ')
|
||||
return
|
||||
endif
|
||||
" "match L C" or "mismatch L C message..."
|
||||
let lnum = str2nr(parts[1])
|
||||
let ccol = str2nr(parts[2])
|
||||
if exists('*setcursorcharpos')
|
||||
call setcursorcharpos(lnum, ccol)
|
||||
else
|
||||
call cursor(lnum, ccol)
|
||||
endif
|
||||
if parts[0] ==# 'mismatch'
|
||||
echohl WarningMsg
|
||||
echo 'Klammertext: ' . join(parts[3:], ' ')
|
||||
echohl None
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! klammertext#Check() abort
|
||||
let script = s:EditScript()
|
||||
if empty(script)
|
||||
echohl ErrorMsg
|
||||
echo 'Klammertext: cannot locate klammertext_edit.py'
|
||||
echohl None
|
||||
return
|
||||
endif
|
||||
let out = systemlist(s:Python() . ' ' . shellescape(script) . ' check',
|
||||
\ getline(1, '$'))
|
||||
if v:shell_error
|
||||
echohl ErrorMsg | echo 'Klammertext: the shared core failed' | echohl None
|
||||
return
|
||||
endif
|
||||
let items = []
|
||||
for line in out
|
||||
let m = matchlist(line, '^\(\d\+\):\(\d\+\): \(.*\)$')
|
||||
if !empty(m)
|
||||
call add(items, {'bufnr': bufnr('%'), 'lnum': str2nr(m[1]),
|
||||
\ 'col': str2nr(m[2]), 'text': m[3], 'type': 'E'})
|
||||
endif
|
||||
endfor
|
||||
call setloclist(0, items, ' ')
|
||||
call setloclist(0, [], 'a', {'title': 'Klammertext delimiter check'})
|
||||
if empty(items)
|
||||
lclose
|
||||
echo 'Klammertext: no delimiter problems found'
|
||||
else
|
||||
lopen
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" --- +python3: in-process indentexpr and live match highlighting ----------
|
||||
" These load the shared core into Vim's embedded Python once, so the =
|
||||
" operator and the per-cursor-move matcher run without spawning processes.
|
||||
|
||||
let s:py_ready = 0
|
||||
|
||||
function! s:PySetup() abort
|
||||
if s:py_ready
|
||||
return 1
|
||||
endif
|
||||
let script = s:EditScript()
|
||||
if empty(script) || !has('python3')
|
||||
return 0
|
||||
endif
|
||||
let g:klammertext_py_dir = fnamemodify(script, ':h')
|
||||
py3 << EOF
|
||||
import sys
|
||||
import vim
|
||||
_kt_dir = vim.eval('g:klammertext_py_dir')
|
||||
if _kt_dir not in sys.path:
|
||||
sys.path.insert(0, _kt_dir)
|
||||
import klammertext_edit as _kt
|
||||
|
||||
def _kt_buffer_and_offset():
|
||||
buf = vim.current.buffer
|
||||
lines = buf[:]
|
||||
s = '\n'.join(lines)
|
||||
row, bytecol = vim.current.window.cursor # bytecol is 0-based bytes
|
||||
line = lines[row - 1] if row <= len(lines) else ''
|
||||
charcol = len(line.encode('utf-8')[:bytecol].decode('utf-8', 'replace'))
|
||||
offset = sum(len(l) + 1 for l in lines[:row - 1]) + charcol
|
||||
return s, lines, offset
|
||||
|
||||
def _kt_indent(lnum):
|
||||
buf = vim.current.buffer
|
||||
lines = buf[:]
|
||||
s = '\n'.join(lines)
|
||||
bol = sum(len(l) + 1 for l in lines[:lnum - 1])
|
||||
col = _kt.target_column(s, bol)
|
||||
return -1 if col is None else col
|
||||
|
||||
def _kt_pos(lines, offset):
|
||||
"""(1-based line, 0-based char col, the line's text) for char OFFSET."""
|
||||
line = 0
|
||||
while line < len(lines) and offset > len(lines[line]):
|
||||
offset -= len(lines[line]) + 1
|
||||
line += 1
|
||||
text = lines[line] if line < len(lines) else ''
|
||||
return (line + 1, offset, text)
|
||||
|
||||
def _kt_match():
|
||||
"""[] when not on a delimiter; else [mismatch, message,
|
||||
l1, c1, len1, (l2, c2, len2)?] with byte columns for matchaddpos()."""
|
||||
s, lines, offset = _kt_buffer_and_offset()
|
||||
m = _kt.match_at(s, offset)
|
||||
if m is None:
|
||||
return []
|
||||
out = [1 if m['mismatch'] else 0, m['message'] or '']
|
||||
for tok in [m['token'], m['match_token']]:
|
||||
if tok is None:
|
||||
continue
|
||||
line, ccol, text = _kt_pos(lines, tok[0])
|
||||
bcol = len(text[:ccol].encode('utf-8')) + 1
|
||||
blen = len(text[ccol:ccol + (tok[1] - tok[0])].encode('utf-8'))
|
||||
out.extend([line, bcol, blen])
|
||||
return out
|
||||
EOF
|
||||
let s:py_ready = 1
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! klammertext#IndentExpr() abort
|
||||
if !s:PySetup()
|
||||
return -1
|
||||
endif
|
||||
return py3eval('_kt_indent(' . v:lnum . ')')
|
||||
endfunction
|
||||
|
||||
function! s:ClearMatchHighlight() abort
|
||||
if exists('w:klammertext_match_ids')
|
||||
for id in w:klammertext_match_ids
|
||||
silent! call matchdelete(id)
|
||||
endfor
|
||||
endif
|
||||
let w:klammertext_match_ids = []
|
||||
endfunction
|
||||
|
||||
function! s:UpdateMatchHighlight() abort
|
||||
call s:ClearMatchHighlight()
|
||||
let r = py3eval('_kt_match()')
|
||||
if empty(r)
|
||||
return
|
||||
endif
|
||||
let group = r[0] ? 'KlammertextMismatch' : 'KlammertextMatch'
|
||||
let pos = []
|
||||
let i = 2
|
||||
while i + 3 <= len(r)
|
||||
call add(pos, [r[i], r[i + 1], r[i + 2]])
|
||||
let i += 3
|
||||
endwhile
|
||||
if !empty(pos)
|
||||
call add(w:klammertext_match_ids, matchaddpos(group, pos))
|
||||
endif
|
||||
if r[0] && !empty(r[1])
|
||||
echo 'Klammertext: ' . r[1]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! klammertext#SetupMatchHighlight() abort
|
||||
if !s:PySetup()
|
||||
return
|
||||
endif
|
||||
hi def link KlammertextMatch MatchParen
|
||||
hi def KlammertextMismatch guifg=#ff5555 gui=bold ctermfg=203 cterm=bold
|
||||
augroup klammertextMatch
|
||||
autocmd! * <buffer>
|
||||
autocmd CursorMoved,CursorMovedI <buffer> call s:UpdateMatchHighlight()
|
||||
autocmd BufLeave,WinLeave <buffer> call s:ClearMatchHighlight()
|
||||
augroup END
|
||||
endfunction
|
||||
10
doc/edit/vim/ftdetect/klammertext.vim
Normal file
10
doc/edit/vim/ftdetect/klammertext.vim
Normal file
@@ -0,0 +1,10 @@
|
||||
" Klammertext filetype detection (.kt source files, .k klammer definitions).
|
||||
"
|
||||
" NOTE: .kt is also Kotlin's extension, and recent Vim/Neovim runtimes map
|
||||
" *.kt to the kotlin filetype. This file overrides that unconditionally
|
||||
" (`set filetype=`, not `setfiletype`, so it wins over the runtime's
|
||||
" earlier detection). If you edit both Kotlin and Klammertext, replace the
|
||||
" *.kt line with a content heuristic of your choice, or drop it and set the
|
||||
" filetype per file with a modeline (# vim: ft=klammertext) or :set.
|
||||
au BufRead,BufNewFile *.kt set filetype=klammertext
|
||||
au BufRead,BufNewFile *.k set filetype=klammertext
|
||||
63
doc/edit/vim/ftplugin/klammertext.vim
Normal file
63
doc/edit/vim/ftplugin/klammertext.vim
Normal file
@@ -0,0 +1,63 @@
|
||||
" Klammertext filetype plugin: comment format, the structural commands, and
|
||||
" (when Vim has +python3) fast in-process indentation and live delimiter
|
||||
" match highlighting. The implementations are in autoload/klammertext.vim;
|
||||
" the algorithms themselves are the shared Python core
|
||||
" (klammertext_edit.py — see doc/edit/README.md for how it is located).
|
||||
"
|
||||
" Commands (buffer-local):
|
||||
" :[range]KlammertextReindent reindent the range (default: whole buffer)
|
||||
" :KlammertextAlign align the @table enclosing the cursor
|
||||
" :KlammertextJumpToMatch jump between a klammer application's
|
||||
" opening and closing delimiter
|
||||
" :KlammertextCheck unbalanced/mismatched delimiters -> the
|
||||
" location list
|
||||
"
|
||||
" Default mappings (set g:klammertext_no_mappings to define none):
|
||||
" <LocalLeader>i reindent the current line (visual: the selection)
|
||||
" <LocalLeader>a align the enclosing table
|
||||
" <LocalLeader>j jump to the matching delimiter
|
||||
"
|
||||
" Reindentation is EXPLICIT-ONLY: whitespace is content in Klammertext, so
|
||||
" indentkeys is emptied and nothing reformats as a side effect of typing.
|
||||
" With +python3 the ftplugin also sets 'indentexpr', so the = operator
|
||||
" (e.g. ==, gg=G) reindents through the same shared implementation.
|
||||
|
||||
if exists("b:did_ftplugin")
|
||||
finish
|
||||
endif
|
||||
let b:did_ftplugin = 1
|
||||
|
||||
setlocal commentstring=#\ %s
|
||||
setlocal comments=b:#
|
||||
setlocal indentkeys=
|
||||
|
||||
command! -buffer -range=% KlammertextReindent
|
||||
\ call klammertext#Reindent(<line1>, <line2>)
|
||||
command! -buffer KlammertextAlign call klammertext#AlignTable()
|
||||
command! -buffer KlammertextJumpToMatch call klammertext#JumpToMatch()
|
||||
command! -buffer KlammertextCheck call klammertext#Check()
|
||||
|
||||
if !exists("g:klammertext_no_mappings")
|
||||
nnoremap <buffer> <silent> <LocalLeader>i :.KlammertextReindent<CR>
|
||||
xnoremap <buffer> <silent> <LocalLeader>i :KlammertextReindent<CR>
|
||||
nnoremap <buffer> <silent> <LocalLeader>a :KlammertextAlign<CR>
|
||||
nnoremap <buffer> <silent> <LocalLeader>j :KlammertextJumpToMatch<CR>
|
||||
endif
|
||||
|
||||
let b:undo_ftplugin = "setlocal commentstring< comments< indentkeys<"
|
||||
\ . " | delcommand KlammertextReindent"
|
||||
\ . " | delcommand KlammertextAlign"
|
||||
\ . " | delcommand KlammertextJumpToMatch"
|
||||
\ . " | delcommand KlammertextCheck"
|
||||
|
||||
" With +python3 the shared core runs in-process: 'indentexpr' makes the =
|
||||
" operator work, and the matching delimiter is highlighted live as the
|
||||
" cursor sits on one (the show-paren equivalent; a mismatch shows in red
|
||||
" with a message). Without +python3 the commands above still work — they
|
||||
" shell out to python3 — and Neovim users can get live matching from the
|
||||
" language server instead (see doc/edit/README.md).
|
||||
if has('python3')
|
||||
setlocal indentexpr=klammertext#IndentExpr()
|
||||
let b:undo_ftplugin .= " | setlocal indentexpr<"
|
||||
call klammertext#SetupMatchHighlight()
|
||||
endif
|
||||
113
doc/edit/vim/syntax/klammertext.vim
Normal file
113
doc/edit/vim/syntax/klammertext.vim
Normal file
@@ -0,0 +1,113 @@
|
||||
" Vim syntax highlighting for Klammertext (.kt and .k files).
|
||||
" The Vim counterpart of doc/edit/emacs/klammertext-mode.el's highlighting
|
||||
" and doc/edit/sublime/Klammertext.sublime-syntax.
|
||||
"
|
||||
" What it highlights (the same token classes as the other editors):
|
||||
"
|
||||
" Text removal (#):
|
||||
" # ... remove to end of line (marker + removed text)
|
||||
" ## ... remove to end of file (marker + removed text)
|
||||
" #[ ... ]# remove enclosed text, nestable (markers + removed)
|
||||
" #- #+ #/ whitespace operators: NOT removals, left unhighlighted
|
||||
"
|
||||
" 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; the
|
||||
" two characters are consumed as one (unhighlighted) unit, so the escaped
|
||||
" character is never read as a delimiter. A run of carets pairs
|
||||
" left-to-right, reproducing the language's parity rule.
|
||||
"
|
||||
" Literal klammers: @code ... code@ — the interior is verbatim (no # or @
|
||||
" interpreted). SYNC: the literal-klammer set's source of truth is
|
||||
" LITERAL_KLAMMERS in doc/edit/shared/klammertext_edit.py; a static
|
||||
" syntax file cannot read it, so when you add a literal klammer 'foo',
|
||||
" copy the klammertextVerbatim region below with code -> foo (and mirror
|
||||
" it in the Emacs, Sublime, and VS Code artifacts; all are seeded with
|
||||
" just 'code').
|
||||
"
|
||||
" How open vs. close is decided (the same rule as every other integration):
|
||||
" a delimiter whose NAME follows the @-run (@name) is an OPENING; a bare
|
||||
" @-run, or one whose NAME precedes it (name@), is a CLOSING. The
|
||||
" look-ahead \%(\w\|@\)\@! on every closing keeps 'foo@bar' correct: that @
|
||||
" is followed by a name, so it opens @bar and 'foo' stays plain text. The
|
||||
" abbreviated @name-arg form colors only @name (the name ends at the first
|
||||
" hyphen), exactly like the other editors.
|
||||
"
|
||||
" Colors come from the shared Klammertext palette
|
||||
" (notes/klammertext_palette.md in the development tree): application blue,
|
||||
" definition green, system orange; each opening bright and its close the
|
||||
" same hue darker; full intensity on dark backgrounds, deepened (0.60x) on
|
||||
" light. All groups are `hi def`, so :highlight in your vimrc overrides.
|
||||
" Delimiter matching (jump + live highlight) is not a tokenizer concern —
|
||||
" it lives in the ftplugin/autoload files.
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
" --- escapes: ^X makes X literal; consumed so # / @ are not delimiters ----
|
||||
" (Defined first; it wins by the earlier-start rule, since the ^ precedes.)
|
||||
syn match klammertextEscape /\^./
|
||||
|
||||
" --- text removal (#) -----------------------------------------------------
|
||||
" Order matters: at the same start position, the LAST defined item wins.
|
||||
syn match klammertextRemovedLine /#.*$/ contains=klammertextMarkerLine
|
||||
syn match klammertextMarkerLine /#/ contained
|
||||
" whitespace operators #- #+N #/N : not removals, left unhighlighted
|
||||
syn match klammertextWhitespaceOp "#[-+/]\d*"
|
||||
syn region klammertextRemovedBlock matchgroup=klammertextMarker start=/#\[/ end=/\]#/ contains=klammertextRemovedBlock
|
||||
syn region klammertextRemovedFile matchgroup=klammertextMarker start=/##/ end=/\%$/
|
||||
|
||||
" --- system / target commands @@@ ----------------------------------------
|
||||
syn match klammertextSysOpen /@\@1<!@@@\w\+/
|
||||
syn match klammertextSysClose /@\@1<!@@@\%(\w\|@\)\@!/
|
||||
syn match klammertextSysClose /@\@1<!\w\+@@@\%(\w\|@\)\@!/
|
||||
|
||||
" --- klammer definitions @@ ----------------------------------------------
|
||||
syn match klammertextDefOpen /@\@1<!@@\w\+/
|
||||
syn match klammertextDefClose /@\@1<!@@\%(\w\|@\)\@!/
|
||||
syn match klammertextDefClose /@\@1<!\w\+@@\%(\w\|@\)\@!/
|
||||
|
||||
" --- klammer applications @ ----------------------------------------------
|
||||
syn match klammertextAppOpen /@\@1<!@\w\+/
|
||||
syn match klammertextAppClose /@\@1<!@\%(\w\|@\)\@!/
|
||||
syn match klammertextAppClose /@\@1<!\w\+@\%(\w\|@\)\@!/
|
||||
|
||||
" --- literal klammer: interior verbatim (seeded default: @code) -----------
|
||||
" Defined AFTER the @-tier matches: in Vim, when several items match at the
|
||||
" same position the LAST defined wins, and this region must beat the plain
|
||||
" klammertextAppOpen match at '@code'. (Sublime's tokenizer picks the FIRST
|
||||
" listed rule — the opposite convention; don't copy that ordering here.)
|
||||
syn region klammertextVerbatim matchgroup=klammertextAppOpen start=/@\@1<!@code\%(\w\)\@!/ matchgroup=klammertextAppClose end=/code@/
|
||||
|
||||
" --- colors ---------------------------------------------------------------
|
||||
" The shared palette, dark and light values (see the header). cterm values
|
||||
" are the nearest xterm-256 approximations.
|
||||
if &background ==# 'light'
|
||||
hi def klammertextAppOpen guifg=#528599 ctermfg=66
|
||||
hi def klammertextAppClose guifg=#426a7a ctermfg=60
|
||||
hi def klammertextDefOpen guifg=#758b55 ctermfg=101
|
||||
hi def klammertextDefClose guifg=#5e7044 ctermfg=101
|
||||
hi def klammertextSysOpen guifg=#996743 ctermfg=94
|
||||
hi def klammertextSysClose guifg=#7a5236 ctermfg=94
|
||||
hi def klammertextMarker guifg=#994040 ctermfg=131
|
||||
hi def klammertextRemoved guifg=#9a9a9a ctermfg=247
|
||||
else
|
||||
hi def klammertextAppOpen guifg=#89ddff ctermfg=117
|
||||
hi def klammertextAppClose guifg=#6eb1cc ctermfg=74
|
||||
hi def klammertextDefOpen guifg=#c3e88d ctermfg=150
|
||||
hi def klammertextDefClose guifg=#9cba71 ctermfg=107
|
||||
hi def klammertextSysOpen guifg=#ffab70 ctermfg=216
|
||||
hi def klammertextSysClose guifg=#cc895a ctermfg=173
|
||||
hi def klammertextMarker guifg=#ff6b6b ctermfg=210
|
||||
hi def klammertextRemoved guifg=#8a8272 ctermfg=101
|
||||
endif
|
||||
hi def link klammertextMarkerLine klammertextMarker
|
||||
hi def link klammertextRemovedLine klammertextRemoved
|
||||
hi def link klammertextRemovedBlock klammertextRemoved
|
||||
hi def link klammertextRemovedFile klammertextRemoved
|
||||
|
||||
let b:current_syntax = "klammertext"
|
||||
Reference in New Issue
Block a user