Use this extension to format, lint, test and evaluate Clojure from Vis. To catch or repair broken source after edits, see Keep files parseable. For the shared language result types, see vis-lang-interface.
Formatting, lint, tests, evaluation and full reader validation use a Clojure library published to Clojars. Structural repair runs locally in Python. It proposes changes without starting a JVM. The full Clojure reader must still accept each repair before a hook can use it.
vis-agent extension install Blockether/vis-lang-clojure --subdirectory extension --global --trustYou need the Clojure CLI and a JDK on PATH. The
library itself is fetched from Clojars the first time a tool runs.
clj.format_code(["src"]) # zprint, or cljfmt when that is the project's config
clj.format_code(source="(defn f [x](* x 2))") # format a snippet, nothing written
clj.lint_code(["src"]) # clj-kondo + reflection warnings
clj.run_tests(["test"]) # Lazytest and clojure.test
clj.run_tests(["test/app/core_test.clj::adds"]) # one test
clj.repl_start(cwd="~/app", aliases=["dev"]) # a project nREPL for this session
clj.repl_eval("(+ 1 1)", cwd="~/app") # evaluate in that REPL
clj.repl_connect(cwd="~/app", port=7888) # or attach to an nREPL you already run
clj.repl_stop(cwd="~/app")Tests reuse a running REPL when there is one, and otherwise run in a clean JVM.
Syntax checks run automatically through edit hooks, not through a public tool.
The extension checks changed .clj, .cljs, .cljc, .cljx, .bb and .edn files with the
Clojure reader. The reader only reads: #= and record literals are reported, not evaluated.
Other reader tags stay data, and an alias needs no loaded namespace.
format_codechanges layout only. It does not repair broken structure.- Before a
patchwrites, the guard tries a conservative repair of the complete proposed file. It keeps the edited lines, original text and line endings as evidence. The reader must accept the candidate before one atomic write. The patch reports each correction. If no safe repair exists, a patch that breaks a parseable file is refused. - After a Python block, the guard checks changed files and can repair them. These checks happen after writes. They do not make the block transactional or roll back its effects.
- Repair notes and diffs appear in
session["clojure_syntax_repairs"]. Unresolved files stay insession["clojure_syntax_errors"]until they parse again.
Most repairs only add missing delimiters. The original text can also prove a narrow closer or quote correction. A generic deletion, a retyped delimiter or a change to unrelated code is refused. The local engine does not replace full reader validation, which still needs a JVM. This workflow needs a Vis host that supports repair decisions from operation hooks.
The guard is SyntaxGuard from
vis-lang-interface,
which describes the shared rules.
extension/ is the Python side. It resolves the library's classpath once, in a cache directory
of its own —
clojure -Sdeps '{:deps {com.blockether/vis-lang-clojure {:mvn/version "1.10.0"}}}' -Spath
— and then runs
java -cp "$classpath" clojure.main -m com.blockether.vis.lang.clojure.cli
once per project root, inside that project. Resolving away from the project is deliberate: your
deps.edn decides what your REPL and your test runs see, never what these tools themselves run
on, so a project pinning an older Clojure — or one whose dependencies come from a repository the
tools cannot reach — still formats, lints and tests. The two sides exchange one JSON object per
line with each other. The Clojure side
(src/com/blockether/vis/lang/clojure/) runs the language tools and the full reader.
The Python side maps results to
vis-lang-interface types and owns structural repair.
When Vis confines a session, a tool may write to the workspace and to Vis' own state directory,
and everything it sends leaves through a local proxy that asks for a credential and terminates
TLS with its own certificate authority. Programs that read https_proxy need nothing more, but a
JVM takes its proxy from system properties and Maven takes its from settings.xml, so a confined
Clojure run would fail to reach Clojars or Maven Central at all.
This extension prepares both before the JVM starts. You do not configure anything: it points the
JVM at the proxy, builds a trust store from the sandbox's certificate bundle, and writes a
settings.xml with the proxy and its credential. That generated settings.xml lives under
~/.vis/lang/vis-lang-clojure/<version>/jail, never in your own ~/.m2: a file of Vis' making does
not belong in a directory you keep yours in.
The caches are shared on purpose. Together with the workspace and Vis' own state directory, a
confined run is granted your Maven repository (~/.m2/repository, or MAVEN_LOCAL_REPO when you
keep it elsewhere), your git-dependency cache (~/.gitlibs, or GITLIBS) and, when it exists, your
tools.deps configuration directory (CLJ_CONFIG, else $XDG_CONFIG_HOME/clojure, else
~/.clojure); everything else is refused. So a confined build downloads only what you do not have
yet, and a REPL or test run started for your project resolves the aliases your own terminal
resolves, including those a global deps.edn defines. The tools' own resolution stays out of that:
their configuration and classpath cache live in the jail directory, so nothing a project or your
global setup declares decides what the formatter, the linter or the test runner run on.
Outside a sandbox there is no proxy in the environment, nothing is prepared, and your caches and configuration are used exactly as before.
clojure -M:test # the Clojure library
vis-agent python -m pytest extension/tests -q # the Python glueTo run the extension against this checkout instead of the released jar:
export VIS_LANG_CLOJURE_COMMAND="clojure -Sdeps '{:deps {com.blockether/vis-lang-clojure {:local/root \"$PWD\"}}}' -M -m com.blockether.vis.lang.clojure.cli"That override brings its own classpath and is run as written, in the project directory, so —
unlike the default command — it takes its classpath from that project's deps.edn too.