" 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(':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! * autocmd CursorMoved,CursorMovedI call s:UpdateMatchHighlight() autocmd BufLeave,WinLeave call s:ClearMatchHighlight() augroup END endfunction