Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.language.clojure.core

Clojure language handlers for Vis.

Format/test/REPL are exposed through the generic language facade (format, test, repl_eval, repl_start, repl_stop) — format here does parinfer delimiter repair + cljfmt, and the same repair is registered as the pack's :balance-fn: the foundation's editors call it with the WHOLE spliced file when an edit would not parse, and write the repair only when it stays inside the lines that edit wrote. Both doors are ADD-ONLY: a delimiter you omitted is added back, one you WROTE is never deleted — a lost opening ( and one ) too many are the same string, so deleting is a guess that rewrites code, and the text an edit REPLACED is the only thing that tells the two apart.

Clojure language handlers for Vis.

Format/test/REPL are exposed through the generic language facade
(`format`, `test`, `repl_eval`, `repl_start`, `repl_stop`) —
`format` here does parinfer delimiter repair + cljfmt, and the same repair is
registered as the pack's `:balance-fn`: the foundation's editors call it with
the WHOLE spliced file when an edit would not parse, and write the repair only
when it stays inside the lines that edit wrote. Both doors are ADD-ONLY: a delimiter
 you omitted is added back, one you WROTE is never deleted — a lost opening `(` and
 one `)` too many are the same string, so deleting is a guess that rewrites code, and
 the text an edit REPLACED is the only thing that tells the two apart.
raw docstring

com.blockether.vis.internal.language.clojure.format

Config-driven Clojure source formatter used by clj/edit for format-on-write and by the format_code language-surface verb.

TWO backends live here, and the choice is TRANSPARENT to the language surface — callers just format; this namespace picks the formatter from the config files present around the target path:

  • zprint — when a .zprint.edn/.zprintrc is found walking UP from the path. The project's zprint options map is applied. This is the canonical, reflowing formatter — for this repo it is THE formatter, applied through the repo's own .zprint.edn.
  • cljfmt — when only a .cljfmt.edn/.cljfmt.clj is found (no zprint), or when neither config exists (cljfmt defaults). Conservative: normalizes indentation + whitespace of MULTI-LINE forms but does NOT reflow a one-liner into multiple lines.

When BOTH configs are present, zprint WINS. Backends and their config loaders are resolved on first use, not while registering the language pack at gateway startup. Registering formatting handlers does not need either implementation.

Failure mode: if a backend refuses (parse error, unfamiliar reader macro, anything that throws), the formatter returns the original source unchanged. We never silently corrupt a file because the formatter choked.

Config-driven Clojure source formatter used by `clj/edit` for format-on-write
and by the `format_code` language-surface verb.

TWO backends live here, and the choice is TRANSPARENT to the language
surface — callers just format; this namespace picks the formatter from the
config files present around the target path:

  * zprint  — when a `.zprint.edn`/`.zprintrc` is found walking UP from the
              path. The project's zprint options map is applied. This is the
              canonical, reflowing formatter — for this repo it is THE
              formatter, applied through the repo's own `.zprint.edn`.
  * cljfmt  — when only a `.cljfmt.edn`/`.cljfmt.clj` is found (no zprint), or
              when neither config exists (cljfmt defaults). Conservative:
              normalizes indentation + whitespace of MULTI-LINE forms but does
              NOT reflow a one-liner into multiple lines.

When BOTH configs are present, zprint WINS. Backends and their config loaders
are resolved on first use, not while registering the language pack at gateway
startup. Registering formatting handlers does not need either implementation.

Failure mode: if a backend refuses (parse error, unfamiliar reader macro,
anything that throws), the formatter returns the original source unchanged.
We never silently corrupt a file because the formatter choked.
raw docstring

com.blockether.vis.internal.language.clojure.lint

clj-kondo linting for the Vis language surface.

Runs clj-kondo's programmatic API (clj-kondo.core/run!) — never shells out — over a code string (fed on stdin as -), explicit path(s), or the workspace's default source paths, and returns a uniform result map (STRING keys — crosses the strings-only boundary as a tool :result): {"op" "clj-lint" "error" N "warning" N "info" N "files" N "findings" [...]} where each finding is {"file" "row" "col" "level" "type" "message" "provider"} (every finding names clj-kondo as its provider).

Resolve the analyzer on the first lint request, not when a language pack is registered: non-Clojure sessions do not need its compiler and analysis tables.

clj-kondo linting for the Vis language surface.

Runs clj-kondo's programmatic API (`clj-kondo.core/run!`) — never shells out —
over a code string (fed on stdin as `-`), explicit path(s), or the workspace's
default source paths, and returns a uniform result map (STRING keys — crosses
the strings-only boundary as a tool `:result`):
`{"op" "clj-lint" "error" N "warning" N "info" N "files" N "findings" [...]}`
where each finding is `{"file" "row" "col" "level" "type" "message" "provider"}`
(every finding names clj-kondo as its provider).

Resolve the analyzer on the first lint request, not when a language pack is
registered: non-Clojure sessions do not need its compiler and analysis tables.
raw docstring

com.blockether.vis.internal.language.clojure.nrepl-client

Thin, observable nREPL client for clj/eval.

Connection model:

  • One nrepl.core/connect socket per [host port] key, cached on a defonce atom so we survive (require :reload) during development.
  • ONE long-lived nREPL session per connection, cloned lazily on first use and cached beside the socket — then REUSED by every eval!. This is how Cider/Calva/every editor drives nREPL: no per-eval clone/close round-trip on the hot path (those were what blew the run_tests budget under JVM load), nothing to leak, and session-local state — *1/*2/*3/*e and dynamic set!s — PERSISTS across calls like a real REPL ((def …) was already global; now the whole session is).
  • Stale / closed sockets are detected (IOException / nil message stream) and the entry is evicted — closing the socket and dropping the cached session — so the next call re-dials + re-clones.

Returned shape (success) — STRING keys (crosses the strings-only boundary as a tool :result; enrichment adds "error_message"/"error_data"/"trace"): {"value" "42" ; pr-str of the LAST form's value, or nil "values" ["1" "42"] ; pr-str of every emitted value "out" "hello\n" ; stdout aggregated "err" "" ; stderr aggregated "ns" "user" ; final ns name "status" #{"done"} ; nREPL status set (strings) "ex" nil ; exception class name, when status :ex "root_ex" nil ; root exception class name "ms" 12 ; wall-clock duration "port" 7888 "timed_out" false}

Failure paths throw ex-info with :type :clj/nrepl-* so the Vis tool wrapper can surface a clean error to the model.

Thin, observable nREPL client for `clj/eval`.

Connection model:
  * One `nrepl.core/connect` socket per `[host port]` key, cached
    on a `defonce` atom so we survive `(require :reload)` during
    development.
  * ONE long-lived nREPL session per connection, cloned lazily on
    first use and cached beside the socket — then REUSED by every
    `eval!`. This is how Cider/Calva/every editor drives nREPL: no
    per-eval `clone`/`close` round-trip on the hot path (those were
    what blew the `run_tests` budget under JVM load), nothing
    to leak, and session-local state — `*1`/`*2`/`*3`/`*e` and dynamic
    `set!`s — PERSISTS across calls like a real REPL (`(def …)` was
    already global; now the whole session is).
  * Stale / closed sockets are detected (`IOException` / `nil`
    message stream) and the entry is evicted — closing the socket and
    dropping the cached session — so the next call re-dials + re-clones.

Returned shape (success) — STRING keys (crosses the strings-only boundary
as a tool `:result`; enrichment adds "error_message"/"error_data"/"trace"):
  {"value"      "42"          ; pr-str of the LAST form's value, or nil
   "values"     ["1" "42"]   ; pr-str of every emitted value
   "out"        "hello\n"     ; stdout aggregated
   "err"        ""             ; stderr aggregated
   "ns"         "user"         ; final *ns* name
   "status"     #{"done"}      ; nREPL status set (strings)
   "ex"         nil              ; exception class name, when status :ex
   "root_ex"    nil              ; root exception class name
   "ms"         12               ; wall-clock duration
   "port"       7888
   "timed_out" false}

Failure paths throw `ex-info` with `:type :clj/nrepl-*` so the
Vis tool wrapper can surface a clean error to the model.
raw docstring

com.blockether.vis.internal.language.clojure.nrepl-ctx

Per-turn nREPL resource synchronization for the Clojure pack.

Live state has ONE model-facing home: repl_status. This extension hook probes owned nREPLs and mirrors them into the generic session resource registry — what repl_status and the footer answer from; nothing about a resource rides in ctx. It returns no legacy session["env"]["languages"] contribution.

OWNERSHIP: we surface ONLY the REPLs THIS session started + owns, PLUS any external nREPL the user EXPLICITLY attached via connect (both from repl-manager/session-repls). There is still NO external-port discovery and no .nrepl-port scanning — attachment is explicit consent, never a scan.

Eval defaults to the workspace-root REPL (else the first) when several exist; the result reports which REPL ran. Each mirror carries liveness status and diagnostics from a per-turn probe.

All best-effort: any failure degrades to an empty contribution and never blocks the render.

Per-turn nREPL resource synchronization for the Clojure pack.

Live state has ONE model-facing home: `repl_status`. This extension hook probes
owned nREPLs and mirrors them into the generic session resource registry —
what `repl_status` and the footer answer from; nothing about a resource rides in
ctx. It returns no legacy `session["env"]["languages"]` contribution.

OWNERSHIP: we surface ONLY the REPLs THIS session started + owns, PLUS any
external nREPL the user EXPLICITLY attached via `connect` (both from
`repl-manager/session-repls`). There is still NO external-port discovery and
no `.nrepl-port` scanning — attachment is explicit consent, never a scan.

Eval defaults to the workspace-root REPL (else the first) when several exist;
the result reports which REPL ran. Each mirror carries liveness status and
diagnostics from a per-turn probe.

All best-effort: any failure degrades to an empty contribution and never
blocks the render.
raw docstring

com.blockether.vis.internal.language.clojure.paren-repair

Delimiter repair for Clojure source the model hand-wrote.

Ported from bhauman/clojure-mcp-light (clojure-mcp-light.delimiter-repair, Apache-2.0): repair via parinfer indent-mode, which trusts the INDENTATION to place the missing / extra ( [ { and so matches how the model intended the code to nest. The parinfer-rust shell path + stats/json bits from upstream are dropped; this is the pure JVM path only, over com.blockether/parinferish — Blockether's linear-time rewrite of parinferish 0.8.0.

Two readers, two questions, and neither answers the other's. parinferish says whether the DELIMITERS balance, so the gate is the same reader that performs the repair and a whole file costs one linear scan. edamame says whether text READS as Clojure, which balanced delimiters do not promise: source cut mid-token comes back closed as (:) — balanced, and not a keyword.

fix-delimiters is the entry point, and it repairs WHOLE Clojure source: format runs it before cljfmt, and the pack publishes it as the editors' :balance-fn, which the foundation applies to the whole file an edit would write and keeps only when the repair stays on that edit's own lines. Handing it a partial form instead balances the fragment into a complete one that means something else.

Delimiter repair for Clojure source the model hand-wrote.

Ported from bhauman/clojure-mcp-light (`clojure-mcp-light.delimiter-repair`,
Apache-2.0): repair via parinfer indent-mode, which trusts the INDENTATION to
place the missing / extra `( [ {` and so matches how the model intended the
code to nest. The parinfer-rust shell path + stats/json bits from upstream are
dropped; this is the pure JVM path only, over `com.blockether/parinferish` —
Blockether's linear-time rewrite of parinferish 0.8.0.

Two readers, two questions, and neither answers the other's. parinferish says
whether the DELIMITERS balance, so the gate is the same reader that performs
the repair and a whole file costs one linear scan. edamame says whether text
READS as Clojure, which balanced delimiters do not promise: source cut
mid-token comes back closed as `(:)` — balanced, and not a keyword.

`fix-delimiters` is the entry point, and it repairs WHOLE Clojure source: `format`
runs it before cljfmt, and the pack publishes it as the editors' `:balance-fn`, which
the foundation applies to the whole file an edit would write and keeps only when the
repair stays on that edit's own lines. Handing it a partial form instead balances the
fragment into a complete one that means something else.
raw docstring

com.blockether.vis.internal.language.clojure.reflection

The :general lint provider: Clojure COMPILER warnings — reflection and boxed math.

Unlike clj-kondo (static analysis over source text), these warnings only exist at COMPILE time: the compiler emits them while it resolves interop / code. So this provider COMPILES whatever the lint TARGETS — a lint_code code string, or each source file being linted — in a throwaway namespace that is torn down afterwards, so the running system is never mutated and nothing leaks.

It compiles the code in a throwaway namespace with *warn-on-reflection* and *unchecked-math* :warn-on-boxed bound, captures the compiler's *err* stream, and parses each warning line

Reflection warning, <file>:<row>:<col> - <message> Boxed math warning, <file>:<row>:<col> - <message>

into the uniform lint finding map, tagged "provider" "general": {"file" "row" "col" "level" "warning" "type" "reflection"|"boxed-math" "message" "provider" "general"}.

The `:general` lint provider: Clojure COMPILER warnings — reflection and
boxed math.

Unlike clj-kondo (static analysis over source text), these warnings only
exist at COMPILE time: the compiler emits them while it resolves interop /
code. So this provider COMPILES whatever the lint TARGETS — a `lint_code` code
string, or each source file being linted — in a throwaway namespace that is
torn down afterwards, so the running system is never mutated and nothing leaks.

It compiles the code in a throwaway namespace with `*warn-on-reflection*` and
`*unchecked-math* :warn-on-boxed` bound, captures the compiler's `*err*`
stream, and parses each warning line

  `Reflection warning, <file>:<row>:<col> - <message>`
  `Boxed math warning, <file>:<row>:<col> - <message>`

into the uniform lint finding map, tagged `"provider" "general"`:
`{"file" "row" "col" "level" "warning" "type" "reflection"|"boxed-math"
  "message" "provider" "general"}`.
raw docstring

com.blockether.vis.internal.language.clojure.repl-manager

Owned, session-scoped nREPL lifecycle for the Clojure pack.

OWNERSHIP: each vis SESSION owns its own nREPL subprocess(es). The processes atom is keyed by [session-id dir], so two sessions in the same directory get two independent REPLs and neither can see or stop the other's. A managed REPL lives and dies with THIS vis process — there is NO persistent registry and NO PID re-attach across a vis restart. Restarting vis means a fresh REPL, exactly like the Python pack.

PORT: we PICK a free ephemeral port ourselves and pass it to the launcher EXPLICITLY (nrepl.cmdline --port N, lein repl :headless :port N, bb nrepl-server N), so we always KNOW our port without ever reading a .nrepl-port file back. Any stray .nrepl-port a tool drops in the project is deleted after boot — vis never depends on it and never leaves it behind.

ALIASES: a REPL is ALWAYS booted with the project's :dev :test deps + paths on its classpath (full dependency spec), with the user's :main-opts dropped (our synthetic :vis/nrepl-launch alias appends last so -m nrepl.cmdline wins). Unknown :dev/:test aliases are silently ignored by tools.deps, so this is safe in any project.

ATTACHMENTS: connect! registers an EXTERNAL nREPL the user already runs in a SEPARATE attachments atom, never in processes. They are different kinds: one is a process we own and must eventually kill, the other is an address we were invited to use. Keeping them apart is what lets ONE project have both at once — the managed JVM REPL repl start booted for its .clj, and the shadow-cljs nREPL its own watch runs for the .cljs — instead of the second connect answering "already-running" about the first and handing back a JVM REPL nobody asked for.

Starting/stopping is CORE and ALWAYS allowed — never gated behind a flag.

Owned, session-scoped nREPL lifecycle for the Clojure pack.

OWNERSHIP: each vis SESSION owns its own nREPL subprocess(es). The `processes`
atom is keyed by `[session-id dir]`, so two sessions in the same directory get
two independent REPLs and neither can see or stop the other's. A managed REPL
lives and dies with THIS vis process — there is NO persistent registry and NO
PID re-attach across a vis restart. Restarting vis means a fresh REPL, exactly
like the Python pack.

PORT: we PICK a free ephemeral port ourselves and pass it to the launcher
EXPLICITLY (`nrepl.cmdline --port N`, `lein repl :headless :port N`,
`bb nrepl-server N`), so we always KNOW our port without ever reading a
`.nrepl-port` file back. Any stray `.nrepl-port` a tool drops in the project is
deleted after boot — vis never depends on it and never leaves it behind.

ALIASES: a REPL is ALWAYS booted with the project's `:dev :test` deps + paths
on its classpath (full dependency spec), with the user's `:main-opts` dropped
(our synthetic `:vis/nrepl-launch` alias appends last so `-m nrepl.cmdline`
wins). Unknown `:dev`/`:test` aliases are silently ignored by tools.deps, so
this is safe in any project.

ATTACHMENTS: `connect!` registers an EXTERNAL nREPL the user already runs in a
SEPARATE `attachments` atom, never in `processes`. They are different kinds:
one is a process we own and must eventually kill, the other is an address we
were invited to use. Keeping them apart is what lets ONE project have both at
once — the managed JVM REPL `repl start` booted for its `.clj`, and the
shadow-cljs nREPL its own `watch` runs for the `.cljs` — instead of the second
`connect` answering "already-running" about the first and handing back a JVM
REPL nobody asked for.

Starting/stopping is CORE and ALWAYS allowed — never gated behind a flag.
raw docstring

com.blockether.vis.internal.language.clojure.shadow-cljs

shadow-cljs as the ClojureScript TEST RUNNER: which build runs the tests, how THIS machine invokes shadow-cljs, and the exact argv that runs a narrowed selection. A *_test.cljs never loads on the JVM, so clojure -M:test can no more run it than node can run a .clj — the build is not a preference here, it is the only runtime that exists.

Three facts decide the command, and each has its own honest refusal instead of a guess:

  1. HOW shadow-cljs is installed. node_modules/.bin/shadow-cljs (npm) wins because it is what the project's own npm test runs; a thheller/shadow-cljs dependency in deps.edn runs as clojure -M[:alias] -m shadow.cljs.devtools.cli — the SAME project may carry it either way, and an alias-only dependency needs that alias on the command line or the classpath lacks the namespace being -m'd. Declared in package.json but not installed is answered as npm install, not as "no ClojureScript runner".
  2. WHICH build runs tests. Resolve an explicit build or the sole test build; never guess between suites. :node-test runs headless, :karma drives its own browser, and :browser-test needs a browser RUNTIME that this runner cannot supply. Compiling alone is never evidence of passing tests.
  3. WHAT the run is narrowed to. --config-merge carries namespace focus and disables Node autorun. Compile first, then run Node separately: shadow's autorun does not propagate the child exit status. Print overrides with pr-str; hand-built regexp escapes can make shadow print help and exit ZERO without compiling anything.
shadow-cljs as the ClojureScript TEST RUNNER: which build runs the tests, how
THIS machine invokes shadow-cljs, and the exact argv that runs a narrowed
selection. A `*_test.cljs` never loads on the JVM, so `clojure -M:test` can no
more run it than `node` can run a `.clj` — the build is not a preference here,
it is the only runtime that exists.

Three facts decide the command, and each has its own honest refusal instead of
a guess:

1. HOW shadow-cljs is installed. `node_modules/.bin/shadow-cljs` (npm) wins
   because it is what the project's own `npm test` runs; a
   `thheller/shadow-cljs` dependency in `deps.edn` runs as
   `clojure -M[:alias] -m shadow.cljs.devtools.cli` — the SAME project may
   carry it either way, and an alias-only dependency needs that alias on the
   command line or the classpath lacks the namespace being `-m`'d. Declared in
   `package.json` but not installed is answered as `npm install`, not as
   "no ClojureScript runner".
2. WHICH build runs tests. Resolve an explicit build or the sole test build;
   never guess between suites. :node-test runs headless, :karma drives its
   own browser, and :browser-test needs a browser RUNTIME that this runner
   cannot supply. Compiling alone is never evidence of passing tests.
3. WHAT the run is narrowed to. `--config-merge` carries namespace focus and
   disables Node autorun. Compile first, then run Node separately: shadow's
   autorun does not propagate the child exit status. Print overrides with
   `pr-str`; hand-built regexp escapes can make shadow print help and exit
   ZERO without compiling anything.
raw docstring

com.blockether.vis.internal.language.clojure.shadow-repl

shadow-cljs as the ClojureScript REPL RUNTIME: attaching to the nREPL a shadow-cljs watch already runs, SELECTING the build whose JS runtime an eval must land in, and keeping that selection true for every later eval.

Five facts about that server decide everything here, and every one of them was learned from a live shadow-cljs, not from its wire protocol:

  1. A shadow-cljs nREPL is INDISTINGUISHABLE from a plain JVM one over describe: it advertises no cljs/shadow op and reports only a :clojure version, so dialect detection from describe metadata answers :clj for it. The only honest question is a read-only eval that RESOLVES shadow.cljs.devtools.api inside the server.
  2. The build is selected PER nREPL SESSION — (shadow…api/nrepl-select :app) — and ONE session serves EVERY build of that server, so a selection belongs to the CONNECTION and never to an attachment: selecting :worker makes the next eval that believes it targets :app answer from the worker's runtime, silently and with no error. Every eval therefore re-checks both the session and the build selected in it, never a boolean.
  3. Selecting from a session ALREADY sitting in a build FAILS — the select form is compiled as ClojureScript and dies on No such namespace: shadow.cljs.devtools.api. :cljs/quit first is what makes selection total: it returns such a session to CLJ, and in a session that never left CLJ it is an ordinary keyword evaluating to itself.
  4. nrepl-select answers the SAME watch for build not running whether the build id does not exist or is merely not being watched. The two are told apart BEFORE selecting — by the server's own build config and worker-running? — because they need opposite fixes.
  5. A selected build with NO connected JS runtime neither errors nor hangs: every eval answers No available JS runtime. on :err with a done status. Nothing but a started runtime fixes it, so that answer is turned into the instruction that starts one.
shadow-cljs as the ClojureScript REPL RUNTIME: attaching to the nREPL a
`shadow-cljs watch` already runs, SELECTING the build whose JS runtime an eval
must land in, and keeping that selection true for every later eval.

Five facts about that server decide everything here, and every one of them was
learned from a live shadow-cljs, not from its wire protocol:

1. A shadow-cljs nREPL is INDISTINGUISHABLE from a plain JVM one over
   `describe`: it advertises no cljs/shadow op and reports only a `:clojure`
   version, so dialect detection from describe metadata answers `:clj` for it.
   The only honest question is a read-only eval that RESOLVES
   `shadow.cljs.devtools.api` inside the server.
2. The build is selected PER nREPL SESSION — `(shadow…api/nrepl-select :app)` —
   and ONE session serves EVERY build of that server, so a selection belongs
   to the CONNECTION and never to an attachment: selecting `:worker` makes
   the next eval that believes it targets `:app` answer from the worker's
   runtime, silently and with no error. Every eval therefore re-checks both
   the session and the build selected in it, never a boolean.
3. Selecting from a session ALREADY sitting in a build FAILS — the select form
   is compiled as ClojureScript and dies on `No such namespace:
   shadow.cljs.devtools.api`. `:cljs/quit` first is what makes selection
   total: it returns such a session to CLJ, and in a session that never left
   CLJ it is an ordinary keyword evaluating to itself.
4. `nrepl-select` answers the SAME `watch for build not running` whether the
   build id does not exist or is merely not being watched. The two are told
   apart BEFORE selecting — by the server's own build config and
   `worker-running?` — because they need opposite fixes.
5. A selected build with NO connected JS runtime neither errors nor hangs:
   every eval answers `No available JS runtime.` on `:err` with a `done`
   status. Nothing but a started runtime fixes it, so that answer is turned
   into the instruction that starts one.
raw docstring

com.blockether.vis.internal.language.clojure.test-runner

Run a namespace's tests in the session's ALREADY-RUNNING nREPL (the fast inner loop) or -- the default, and whenever there is no such REPL -- by shelling the project's own test command in a clean JVM. Nothing here EVER starts a REPL.

The in-REPL path is FRAMEWORK-AGNOSTIC: a ns whose vars carry clojure.test :test metadata runs through clojure.test/run-tests; otherwise it is treated as lazytest and run through lazytest.runner/run-tests. Either way the result is a uniform STRING-keyed map (crosses the strings-only boundary) with "mode" (repl or cli), "framework", "ns", "total", "pass", "fail" and "failures" [{"ns" "test" "message" "file" "line"} ...]. CLI and shadow-cljs retain complete stdout/stderr in "output" (ANSI-stripped) and collect per-fault diagnostics before rendering. Only the display preview may be bounded; the result data is never reduced to a log tail.

run-form is the code EVALED on the target nREPL. It is a quoted form (not a call into this namespace) so it works against ANY project's nREPL, including hosts that do not have the vis-agent extension on their classpath.

Run a namespace's tests in the session's ALREADY-RUNNING nREPL (the fast inner
loop) or -- the default, and whenever there is no such REPL -- by shelling the
project's own test command in a clean JVM. Nothing here EVER starts a REPL.

The in-REPL path is FRAMEWORK-AGNOSTIC: a ns whose vars carry clojure.test
:test metadata runs through clojure.test/run-tests; otherwise it is treated
as lazytest and run through lazytest.runner/run-tests. Either way the result
is a uniform STRING-keyed map (crosses the strings-only boundary) with
"mode" (repl or cli), "framework", "ns", "total", "pass", "fail" and
"failures" [{"ns" "test" "message" "file" "line"} ...].
CLI and shadow-cljs retain complete stdout/stderr in "output" (ANSI-stripped)
and collect per-fault diagnostics before rendering. Only the display preview
may be bounded; the result data is never reduced to a log tail.

run-form is the code EVALED on the target nREPL. It is a quoted form (not a
call into this namespace) so it works against ANY project's nREPL, including
hosts that do not have the vis-agent extension on their classpath.
raw docstring

com.blockether.vis.internal.language.python.core

A managed Python REPL exposed through the generic language facade (repl_start / repl_status / repl_stop / repl_eval). Activates only when the workspace looks like a Python project. The REPL is a subprocess on a project-aware interpreter (uv / poetry / .venv / python3), registered as a session resource so it shows in ctx + the footer and is stoppable by id.

A managed Python REPL exposed through the generic
language facade (repl_start / repl_status / repl_stop / repl_eval). Activates
only when the workspace looks like a Python project. The REPL is a subprocess
on a project-aware interpreter (uv / poetry / .venv / python3), registered as
a session resource so it shows in ctx + the footer and is stoppable by id.
raw docstring

com.blockether.vis.internal.language.python.interpreter

Detect WHICH Python launches a REPL or a run_tests shell-out, mirroring how the Clojure pack picks deps.edn / lein / bb. The project-managed environment is detected so the interpreter sees the project's dependencies.

Detect WHICH Python launches a REPL or a `run_tests` shell-out, mirroring how
the Clojure pack picks deps.edn / lein / bb. The project-managed environment
is detected so the interpreter sees the project's dependencies.
raw docstring

com.blockether.vis.internal.language.python.repl-manager

A MANAGED Python REPL: a persistent interpreter subprocess running a tiny line-framed eval server — one JSON request per line in, one JSON response per line out. Globals persist across evals (real REPL state). One process per session and canonical directory; the cached Process handle owns teardown.

A MANAGED Python REPL: a persistent interpreter subprocess running a tiny
line-framed eval server — one JSON request per line in, one JSON response per
line out. Globals persist across evals (real REPL state). One process per
session and canonical directory; the cached `Process` handle owns teardown.
raw docstring

com.blockether.vis.internal.language.python.ruff

format_code / lint_code for Python, backed by ruff (com.blockether/ruff).

ruff runs IN-PROCESS: the Rust ruff_python_formatter / ruff_linter crates are linked as a cdylib and called over the FFM API. There is no ruff binary to install, no subprocess, no virtualenv, and no PATH lookup — the same code path works from the native image. Configuration is RUFF'S OWN discovery: for every source, ruff walks up from the file to the nearest .ruff.toml / ruff.toml / pyproject.toml with a [tool.ruff] table and honours it whole — extend, per-file-ignores, target-version, formatter options — so a run here and a ruff CLI run agree on every source it reads. With no config anywhere the tool still runs on ruff's defaults and SAYS SO in a hint.

ONE divergence, the same one the sandbox ruff shim documents: the FFI is a one-source-one-call linter/formatter, so the WALK is ours and a config's exclude / extend-exclude globs are NOT applied to it — the noise directories in skip-dirs are pruned instead. per-file-ignores DO apply: ruff matches them itself against the path we hand it.

Both handlers accept the SAME argument shapes as the Clojure pack: a code string, {"code"}, {"path"}, {"paths"}, or nothing (whole project).

`format_code` / `lint_code` for Python, backed by ruff (com.blockether/ruff).

ruff runs IN-PROCESS: the Rust ruff_python_formatter / ruff_linter crates are
linked as a cdylib and called over the FFM API. There is no `ruff` binary to
install, no subprocess, no virtualenv, and no PATH lookup — the same code path
works from the native image. Configuration is RUFF'S OWN discovery: for every
source, ruff walks up from the file to the nearest `.ruff.toml` / `ruff.toml` /
`pyproject.toml` with a `[tool.ruff]` table and honours it whole — `extend`,
`per-file-ignores`, `target-version`, formatter options — so a run here and a
`ruff` CLI run agree on every source it reads. With no config anywhere the
tool still runs on ruff's defaults and SAYS SO in a `hint`.

ONE divergence, the same one the sandbox `ruff` shim documents: the FFI is a
one-source-one-call linter/formatter, so the WALK is ours and a config's
`exclude` / `extend-exclude` globs are NOT applied to it — the noise
directories in `skip-dirs` are pruned instead. `per-file-ignores` DO apply:
ruff matches them itself against the path we hand it.

Both handlers accept the SAME argument shapes as the Clojure pack:
a code string, {"code"}, {"path"}, {"paths"}, or nothing (whole project).
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close