Skip to content

Add a scijava-code-lsp ScriptDialect - #1

Draft
gselzer wants to merge 35 commits into
mainfrom
lsp-completion
Draft

gselzer wants to merge 35 commits into
mainfrom
lsp-completion

Conversation

@gselzer

@gselzer gselzer commented Oct 21, 2026

Copy link
Copy Markdown
Member

This PR adds a org.scijava.code.lsp.ScriptDialect that describes how to convert a SciJava Appose Python script to plain python code. There's likely a lot of cleanup to be done here - claude had a big hand in making this code and may have touched some unnecessary files.

See scijava/scijava#294 for discussion

gselzer and others added 30 commits October 6, 2026 10:49
Complete code in appose-python scripts in two layers:

* tree-sitter, in process: names in scope, script parameters, task,
  builtins and keywords, instantly and without any Python.
* Jedi, in a small tooling environment of its own (built in the
  background on first use), analyzing scripts against their own
  environment's interpreter: members, module contents, signatures and
  documentation. Answers come without blocking typing, as updates;
  documentation is fetched on demand.

Completion never builds a script's environment or disturbs its worker:
environments built earlier (in any session) are found read-only.
Dependencies a script declares but its environment lacks are installed
for completion only, into a separate overlay directory, once they stop
changing. Signature help comes from Jedi.

Also extract the engine's environment resolution into ScriptEnvironment,
shared with completion, and add ResidentWorkerService#find, a lookup
that never replaces a running worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parameter preamble becomes ApposePythonDialect, and where the script runs (its environment's Python, its overlay) an EnvironmentContext. The preamble now lists imports first and task last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Jedi tasks (JediClient, jedi_complete.py) give way to jedi-language-server, run from the tooling environment via code-lsp's LspClient; PythonLspClient reads its Python signatures. Tree-sitter's completions, dependency overlays and script environments stay as they were: the overlay and the environment's Python become the server's extra path and environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApposePythonLspServerPlugin offers it via code-lsp's LspService, for this completer and others (e.g. Jython's); PythonLspClient moved to code-lsp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tooling environment gets NumPy: script parameters typed as NumPy arrays (e.g. images) were unknown to the server until the script's own environment was built, so 'image.' offered nothing from it.

jedi-language-server is started via a launcher that exits once this JVM is gone: the server does not notice by itself, and on Windows then loops, failing to write to the closed pipe, flooding its log (which now starts anew beyond 5 MB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Completion now comes from two lsp4j language servers, which code-lsp's
LanguageServerService merges: PythonSyntaxServerPlugin (tree-sitter:
instant names, parameters, builtins and keywords) and JediServerPlugin
(jedi-language-server, against the script's environment plus overlays,
seeing the script as plain Python). ApposePythonEnvironments holds what
the completer knew about environments; ApposePythonCodeCompleter, and
its merge and time budget, are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its remaining types (ScriptDocument, ScriptDialect, ClassIndex,
Signatures, Environment) come from scijava-code-lsp, and script
parameters from the LanguageServerService.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script's environment can become available without the script
changing: when a script run builds it, or when packages it declares are
installed for completion. ResidentWorkerService now notifies listeners
of finished builds, and DependencyOverlays of ready overlays; the jedi
plugin passes both on to LanguageServerService.environmentsChanged(), so
open scripts move to the servers of where they run now. Tests ask
through script sessions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PythonCompletionServer (with DelegatingLanguageServer) completes Java
members on objects jedi cannot see into. It is specific to Python, so
it lives here now, with the one plugin using it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScriptSession.setFile(File) is gone from code-lsp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
code-lsp's merged completions now come once all servers have answered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
code-lsp no longer merges several plugins' servers, nor moves scripts
between them. ApposePythonServerPlugin now offers one server,
ApposePythonLanguageServer: jedi's completions first, then the syntax
server's for other names; jedi's signatures, hover and problems, else
the syntax server's. It moves each script to a jedi of where it runs as
that changes (as its metadata is edited, or its environment is built).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both are ordered places imports are resolved from; each server reads
them as its language does. Nothing read the classpath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jedi and the syntax-only server now read the text the session makes in
ApposePythonDialect. The syntax server no longer reads #@ lines: it
finds the parameters and task among the names declared, with the
types they are annotated with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
E.g. a package imported only to declare a parameter's type is no
longer offered (see ScriptDialect#imported).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
code-lsp now runs server processes from ServerProcessPlugins, replacing
its launch package. JediLanguageServerSpec becomes
JediServerProcessPlugin, a plugin that starts jedi-language-server in
the tooling environment; ApposePythonLspServerPlugin, which offered the
spec, is gone, and ApposePythonEnvironments gives just the tooling
Python.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The language server plugin, its server, the environments it analyzes
scripts against, jedi-language-server's plugin and the syntax-only
server join the rest of the completion code (and their tests, the
rest of its tests). The python package keeps what runs scripts.

ScriptEnvironment, which both resolve scripts' environments with, is
public now; so is what completion uses of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The syntax-only server was its analysis's only user, so the two are
one class now: the analysis (analyze, Context, Kind) is package-private,
for the tests. The dialect is the plugin's, so ApposePythonServerPlugin
keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PythonCompletionServer was the only server built on it, so it passes
on what it does not change itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The syntax-only server (tree-sitter) answered only when jedi had failed,
or with names jedi had not completed: completions waited for jedi
anyway. Gone with it are the merging of the two servers' answers, the
fallbacks from jedi to it, and the tree-sitter dependencies.

Without jedi, the plugin no longer serves Appose Python at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApposePythonEnvironments now gives the environment of each script as
code-lsp asks, before each request, so the script moves to another
jedi process once its environment is built or its overlay is ready.
This replaces the router that relocated scripts between servers, and
the listeners that told it to; PythonCompletionServer becomes
ApposePythonLanguageServer in its place.

Empty answers are no longer reused, as Jedi may know more once asked
again (e.g. after a new dependency's overlay is ready). Everything but
the two plugins is now package-private.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jedi-language-server is now offered by scijava-code-lsp-jedi (a runtime
dependency), so JediServerProcessPlugin goes; the tooling environment
here only installs dependency overlays.

code-lsp's servers now analyze scripts in one environment each. Since a
script's environment is in its text, and changes as it is edited, the
new RoutingLanguageServer keeps a Python server per environment and
moves each script to the one where it runs now, as asked before each
request (and closes servers left without scripts).

Also follow code-lsp's API: plugins say which LSP language they serve
(appose-python), and ask whether python is served rather than catching
its absence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
code-lsp's sessions ask for the LSP language named as the script's
language, in lower case, so the plugin no longer declares the script
language it serves separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApposePythonDialect is now a ScriptDialect plugin: it shows scripts as
plain Python and says which built Appose environment each runs in.
The tuned server, the overlays and the routing moved to code-lsp-jedi
and code-lsp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The completion integration test keeps script environments, image
parameters and problems after #@ lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parameter types are never null, so stop checking for null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialect writes its imports, its parameters' declarations and the
task's in one header line, and names what it imports only for them, to
hide from completion. Its tests now test each of its methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gselzer and others added 5 commits October 16, 2026 17:29
The dialect names its server only by language id: applications choose
their servers, as scripting-groovy now has them do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overlays of packages a script declares, but its environment lacks,
move here from scijava-code-lsp-jedi, whose servers now analyze scripts
in exactly the environment they are given: ApposePythonDialect includes
them in the environment it reports. They are installed with Appose's uv.
Also follows scijava-code-lsp: sessions are made with the service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Completion now builds the environment a script declares, in the
background, once its configuration has stayed the same for a while
(ApposePythonEnvironments): waiting for a script running in it first,
and replacing its worker, as the script's next run would. Completion
then analyzes the script there, conda packages and environment files
included; and the next run need not build it. So the dependency overlays
are gone.

Also, an environment is no longer rebuilt in place while a worker of its
earlier configuration may still be running: ResidentWorker.release()
says when its process has ended, which LazyEnvironment waits for; and
files of an environment being built are not used. Workers are made in
one place (ScriptEnvironment.worker).

FIXME: unsaved scripts are named differently by completion and by runs
from the script editor, so a run builds another environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gselzer gselzer changed the title Lsp completion Add a scijava-code-lsp ScriptDialect Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant