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:
@@ -1,30 +1,39 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# editor_test.sh — Regression tests for the Klammertext editor support
|
||||
# (the Emacs mode's indentation/alignment units and their Sublime Text ports).
|
||||
# (doc/edit/: the shared Python core, the language server, and the Emacs,
|
||||
# Sublime Text, Vim, and VS Code integrations built on them).
|
||||
#
|
||||
# Why this lives in tst/: the editor code implements the LANGUAGE's structural
|
||||
# layer — @-run length, bar-run dimension, ^-escapes, # removal, literal
|
||||
# spans, nesting depth — independently of both the SKS and the Klammermachine.
|
||||
# tst/ is the SKS-independent tier, and these suites check that the
|
||||
# independent implementations of Klammertext's structure agree with the
|
||||
# canonical formatting conventions AND with each other. The klammer names
|
||||
# that appear in the fixtures (@ol, @table, @document, @code) are seeded
|
||||
# configuration of the editor tools, not SKS dependencies; nothing here runs
|
||||
# ktext or loads a klammer set.
|
||||
# implementations of Klammertext's structure agree with the canonical
|
||||
# formatting conventions AND with each other. The klammer names that appear
|
||||
# in the fixtures (@ol, @table, @document, @code) are seeded configuration of
|
||||
# the editor tools, not SKS dependencies; nothing here runs ktext or loads a
|
||||
# klammer set.
|
||||
#
|
||||
# What is checked, for every fixture pair <name>.kt / <name>_expected.kt in
|
||||
# tst/editor/:
|
||||
# * the Sublime Python core's output equals the expected file
|
||||
# * the shared core's output equals the expected file — through the Python
|
||||
# API (what Sublime uses) AND through the core's CLI (what Vim uses)
|
||||
# * idempotence: the tool applied to the expected file leaves it unchanged
|
||||
# * when Emacs is installed: the Emacs unit's output equals the expected
|
||||
# file, and equals the Python output byte for byte (the sync check across
|
||||
# the SYNC-noted policy lists) — skipped gracefully otherwise
|
||||
# * the three Sublime adapter plugins import outside Sublime and resolve
|
||||
# to the shared core
|
||||
# * the language server passes its protocol test (tst/editor/ls_test.py:
|
||||
# handshake, diagnostics, formatting, matching, alignTable — the VS Code
|
||||
# extension's whole surface)
|
||||
# * when Emacs is installed: the Emacs units' output equals the expected
|
||||
# file, and equals the shared core's output byte for byte (the sync
|
||||
# check across the independent elisp implementation) — skipped otherwise
|
||||
# * when a Vim with +eval is installed: the Vim plugin's commands produce
|
||||
# the same bytes (exercising the CLI shell-out glue) — skipped otherwise
|
||||
#
|
||||
# The editor code is located automatically: doc/emacs + doc/sublime in the
|
||||
# development tree, doc/edit/emacs + doc/edit/sublime in the distribution.
|
||||
# The editor code lives in doc/edit/ (same layout in the development tree and
|
||||
# the distribution).
|
||||
#
|
||||
# Usage: ./editor_test.sh Requires python3; Emacs is optional.
|
||||
# Usage: ./editor_test.sh Requires python3; Emacs and Vim optional.
|
||||
# Exit code: 0 if all tests pass, 1 otherwise.
|
||||
|
||||
PASS=0
|
||||
@@ -37,16 +46,15 @@ reset=$'\033[0m'
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/.." && pwd)"
|
||||
if [ -d "$ROOT/doc/emacs" ]; then
|
||||
EMACS_DIR="$ROOT/doc/emacs"
|
||||
SUBLIME_DIR="$ROOT/doc/sublime"
|
||||
elif [ -d "$ROOT/doc/edit/emacs" ]; then
|
||||
EMACS_DIR="$ROOT/doc/edit/emacs"
|
||||
SUBLIME_DIR="$ROOT/doc/edit/sublime"
|
||||
else
|
||||
echo "editor_test.sh: cannot locate the editor support (doc/emacs or doc/edit/emacs)" >&2
|
||||
EDIT_DIR="$ROOT/doc/edit"
|
||||
if [ ! -d "$EDIT_DIR/emacs" ]; then
|
||||
echo "editor_test.sh: cannot locate the editor support (doc/edit)" >&2
|
||||
exit 1
|
||||
fi
|
||||
EMACS_DIR="$EDIT_DIR/emacs"
|
||||
SUBLIME_DIR="$EDIT_DIR/sublime"
|
||||
SHARED_DIR="$EDIT_DIR/shared"
|
||||
VIM_DIR="$EDIT_DIR/vim"
|
||||
FIX="$HERE/editor"
|
||||
OUT="$(mktemp -d)"
|
||||
trap 'rm -rf "$OUT"' EXIT
|
||||
@@ -62,25 +70,62 @@ py_args=()
|
||||
el_args=()
|
||||
add_fixture() { # add_fixture MODE NAME
|
||||
local mode="$1" name="$2"
|
||||
py_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.py.out")
|
||||
py_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.py.idem")
|
||||
el_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.el.out")
|
||||
el_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.el.idem")
|
||||
py_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.py.out")
|
||||
py_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.py.idem")
|
||||
py_args+=("$mode-cli" "$FIX/$name.kt" "$OUT/$name.cli.out")
|
||||
el_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.el.out")
|
||||
el_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.el.idem")
|
||||
}
|
||||
for f in $INDENT_FIXTURES; do add_fixture indent "$f"; done
|
||||
for f in $ALIGN_FIXTURES; do add_fixture align "$f"; done
|
||||
|
||||
# --- run the Sublime Python cores (required) -------------------------------
|
||||
check() { # check NAME FILE_A FILE_B
|
||||
if diff -q "$2" "$3" >/dev/null 2>&1; then
|
||||
echo "${green}PASS${reset} $1"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} $1"
|
||||
diff "$2" "$3" | head -10
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# --- the shared core: API and CLI (required) -------------------------------
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "editor_test.sh: python3 not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/editor_driver.py" "$SUBLIME_DIR" "${py_args[@]}"; then
|
||||
if ! PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/editor_driver.py" "$SHARED_DIR" "${py_args[@]}"; then
|
||||
echo "editor_test.sh: the Python driver failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- run the Emacs units (optional) ----------------------------------------
|
||||
# --- the Sublime adapters resolve to the shared core -----------------------
|
||||
if PYTHONDONTWRITEBYTECODE=1 python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '$SUBLIME_DIR')
|
||||
import Klammertext, Klammertext_indent, Klammertext_align
|
||||
assert Klammertext.KE.__file__.startswith('$SHARED_DIR')
|
||||
" 2>"$OUT/sublime.log"; then
|
||||
echo "${green}PASS${reset} sublime adapters import the shared core"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} sublime adapters import the shared core"
|
||||
cat "$OUT/sublime.log"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# --- the language server protocol test -------------------------------------
|
||||
if PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then
|
||||
echo "${green}PASS${reset} language server protocol ($(grep -c '^PASS' "$OUT/ls.log") checks)"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} language server protocol:"
|
||||
cat "$OUT/ls.log"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# --- the Emacs units (optional) --------------------------------------------
|
||||
HAVE_EMACS=0
|
||||
if command -v emacs >/dev/null 2>&1; then
|
||||
if emacs --batch -L "$EMACS_DIR" \
|
||||
@@ -93,29 +138,93 @@ if command -v emacs >/dev/null 2>&1; then
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
else
|
||||
echo "(Emacs not installed — the Emacs half is skipped; the Python cores still run)"
|
||||
echo "(Emacs not installed — the Emacs half is skipped; the shared core still runs)"
|
||||
fi
|
||||
|
||||
# --- the Vim plugin (optional) ---------------------------------------------
|
||||
HAVE_VIM=0
|
||||
VIM_BIN="$(command -v vim || true)"
|
||||
if [ -n "$VIM_BIN" ] && "$VIM_BIN" --version 2>/dev/null | grep -q '+eval'; then
|
||||
HAVE_VIM=1
|
||||
run_vim() { # run_vim MODE INFILE OUTFILE
|
||||
local cmd
|
||||
if [ "$1" = indent ]; then
|
||||
cmd='KlammertextReindent'
|
||||
else
|
||||
cmd='call search("|") | KlammertextAlign'
|
||||
fi
|
||||
"$VIM_BIN" -N -n -u NONE -i NONE -es --not-a-term \
|
||||
--cmd "set rtp^=$VIM_DIR" \
|
||||
-c 'filetype plugin on' \
|
||||
-c "edit! $2" -c 'set ft=klammertext' \
|
||||
-c "$cmd" -c "saveas! $3" -c 'qa!' </dev/null >/dev/null 2>&1
|
||||
}
|
||||
for f in $INDENT_FIXTURES; do
|
||||
run_vim indent "$FIX/$f.kt" "$OUT/$f.vim.out"
|
||||
run_vim indent "$FIX/${f}_expected.kt" "$OUT/$f.vim.idem"
|
||||
done
|
||||
for f in $ALIGN_FIXTURES; do
|
||||
run_vim align "$FIX/$f.kt" "$OUT/$f.vim.out"
|
||||
run_vim align "$FIX/${f}_expected.kt" "$OUT/$f.vim.idem"
|
||||
done
|
||||
# The comprehensive plugin checks: ftdetect (Kotlin override), syntax
|
||||
# token classes, jump-to-match (incl. multibyte columns), the location-
|
||||
# list check, and — with +python3 — indentexpr (gg=G) and the live
|
||||
# match highlighter. Sections skip inside the driver per Vim feature.
|
||||
if KT_FIX="$FIX" KT_OUT="$OUT/vim_feature.txt" \
|
||||
"$VIM_BIN" -N -n -u NONE -i NONE -es --not-a-term \
|
||||
--cmd "set rtp^=$VIM_DIR" \
|
||||
-c "source $FIX/vim_feature_test.vim" </dev/null >/dev/null 2>&1; then
|
||||
echo "${green}PASS${reset} vim plugin features ($(grep -c '^PASS' "$OUT/vim_feature.txt") checks$(
|
||||
grep -q '^SKIP' "$OUT/vim_feature.txt" && printf '; %s' "$(grep -c '^SKIP' "$OUT/vim_feature.txt") section(s) skipped"))"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} vim plugin features:"
|
||||
grep -v '^PASS' "$OUT/vim_feature.txt" 2>/dev/null || echo " (the Vim driver itself failed)"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
else
|
||||
echo "(no Vim with +eval installed — the Vim half is skipped; the shared core still runs)"
|
||||
fi
|
||||
|
||||
# --- the VS Code extension (optional: needs a Node runtime) ----------------
|
||||
# node itself, or VS Code's Electron binary run as Node.
|
||||
run_node() {
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
node "$@"
|
||||
elif [ -x /usr/share/code/code ]; then
|
||||
ELECTRON_RUN_AS_NODE=1 /usr/share/code/code "$@"
|
||||
else
|
||||
return 127
|
||||
fi
|
||||
}
|
||||
if command -v node >/dev/null 2>&1 || [ -x /usr/share/code/code ]; then
|
||||
if run_node "$FIX/vscode_ext_test.js" "$EDIT_DIR/vscode" "$FIX" >"$OUT/vscode.log" 2>&1; then
|
||||
echo "${green}PASS${reset} vscode extension ($(grep -c '^PASS' "$OUT/vscode.log") checks)"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} vscode extension:"
|
||||
cat "$OUT/vscode.log"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
else
|
||||
echo "(no Node runtime — the VS Code extension test is skipped; the language server is still tested)"
|
||||
fi
|
||||
|
||||
# --- compare ---------------------------------------------------------------
|
||||
check() { # check NAME FILE_A FILE_B
|
||||
if diff -q "$2" "$3" >/dev/null 2>&1; then
|
||||
echo "${green}PASS${reset} $1"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} $1"
|
||||
diff "$2" "$3" | head -10
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
for f in $INDENT_FIXTURES $ALIGN_FIXTURES; do
|
||||
check "$f (python)" "$OUT/$f.py.out" "$FIX/${f}_expected.kt"
|
||||
check "$f (python idempotent)" "$OUT/$f.py.idem" "$FIX/${f}_expected.kt"
|
||||
check "$f (cli)" "$OUT/$f.cli.out" "$FIX/${f}_expected.kt"
|
||||
if [ "$HAVE_EMACS" = 1 ]; then
|
||||
check "$f (emacs)" "$OUT/$f.el.out" "$FIX/${f}_expected.kt"
|
||||
check "$f (emacs idempotent)" "$OUT/$f.el.idem" "$FIX/${f}_expected.kt"
|
||||
check "$f (emacs == python)" "$OUT/$f.el.out" "$OUT/$f.py.out"
|
||||
fi
|
||||
if [ "$HAVE_VIM" = 1 ]; then
|
||||
check "$f (vim)" "$OUT/$f.vim.out" "$FIX/${f}_expected.kt"
|
||||
check "$f (vim idempotent)" "$OUT/$f.vim.idem" "$FIX/${f}_expected.kt"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
Reference in New Issue
Block a user