VS Code integration for the Elide build tool. The extension turns an elide.pkl project into a
model the Kotlin LSP by JetBrains can
import — resolved Maven dependencies, source roots, JDK — and adds Elide build/run/test tasks and JDWP debugging.
Until the extension is on the Marketplace, install the .vsix from plugins.elide.dev (the same worker that hosts the
IntelliJ plugin repository). The link is version-less and always resolves to the current release:
curl -LOJ "https://plugins.elide.dev/vscode/files?id=elide.elide" # → elide-<version>.vsix
code --install-extension elide-*.vsixOr in VS Code: Extensions ▸ … ▸ Install from VSIX…. https://plugins.elide.dev/vscode lists the published version
and a pinned (&version=) link.
- Elide 1.5+ installed (
~/.local/share/elide,$ELIDE_HOME, orelideonPATH). - The Kotlin by JetBrains extension (
JetBrains.kotlin-server, ≥ 0.0.11). It is declared as an extension dependency and installed automatically; complete its one-time region / data-sharing setup so the language server starts. - A JDK for symbol resolution (
$JAVA_HOME, SDKMAN,/Library/Java/JavaVirtualMachines,/usr/lib/jvm, …). Elide's bundled JDK image cannot serve this role: it ships without areleasefile, which IntelliJ needs to enumerate modules.
Opening a folder that contains elide.pkl runs a sync:
elide manifest— the resolved project manifest as JSON (source sets, Kotlin compiler options, entrypoints).elide install— only when the lockfile (.dev/elide.lock*.bin) is older than the manifest.elide classpath <source set>:compilefor every compilable source set.workspace.jsonis written at the workspace folder root in the Kotlin LSP's JSON workspace format (one module per source set, one library per jar with-sources.jar/-javadoc.jarattached when present, the selected JDK as SDK, Kotlin language/API level and free compiler args as kotlinc flags).- Two Kotlin LSP settings are maintained:
intellij.buildToolis pinned tojsonin the workspace, andintellij.jdkForSymbolResolutionis written to user settings. A running Kotlin LSP is then asked to reload.
workspace.json is a generated artifact — add it to .gitignore. Every elide.pkl under a workspace folder (outside
.dev/ and node_modules/) becomes a set of modules in that folder's single workspace.json. A manifest nested
inside another project's directory — a vendored checkout, a sample, a fixture — is a separate build the enclosing
project does not invoke: only the outermost manifest of each tree is imported, and changes to the nested ones do not
trigger a sync.
The Kotlin LSP only reads workspace.json when no build system claims the folder first: it matches jps
(.idea/modules.xml), gradle, maven, and bazel before falling back to the JSON importer, and asks the user to
choose when several match. A checked-in .idea from a teammate on IntelliJ would therefore be imported instead of the
Elide model. Pinning intellij.buildTool to json skips detection, so .idea and workspace.json coexist: IntelliJ
ignores workspace.json, VS Code ignores .idea. The pin is skipped when the window holds a folder without an Elide
project (the setting is window-scoped and would disable that folder's Gradle/Maven import) — set it per workspace by
hand there. An explicit intellij.buildTool in user, workspace, or folder settings is never overwritten.
| Setting | Scope written | Why |
|---|---|---|
intellij.buildTool |
workspace (.vscode/settings.json) |
json; portable, correct for everyone who opens the repo in VS Code. Safe to commit. |
intellij.jdkForSymbolResolution |
user settings | An absolute JDK path for this machine. Committed, it breaks every other checkout: the importer rejects a defaultSdk that is not a directory and the whole import fails. |
A workspace or folder value of intellij.jdkForSymbolResolution that does not resolve on this machine (a path
committed by a teammate, or written by an older version of this extension) is cleared on sync; one that does resolve is
treated as a deliberate project override and kept. In user settings, only a value this extension wrote is updated —
anything you set by hand stays.
| Command | Description |
|---|---|
Elide: Sync Project(s) |
Re-run the sync. |
Elide: Show Menu |
Quick pick with the Elide actions (what the status bar item opens). |
Elide: Run Elide Command… |
Pick build, test, install, or run <entrypoint> and run it as a task. |
Elide: Open generated Kotlin LSP workspace |
Open workspace.json. |
Elide: Show Output |
Open the Elide output channel (CLI output, sync log). |
Task type elide with command (build | run | test | install) and project (root relative to the
workspace folder). Provided tasks: elide: build, elide: test, elide: install, and one elide: run … per
manifest entrypoint (entrypoint, jvm.main, scripts).
The rest of the definition is the invocation:
| Field | Becomes | Example |
|---|---|---|
args |
positional arguments of the subcommand | ["compile"] → elide build compile |
flags |
-f NAME[=VALUE] build flags, read by the manifest as build.flags |
["release"] → -f release |
options |
CLI options, keyed by name | { "no-cache": true, bail: 3 } → --no-cache --bail=3 |
programArgs |
arguments after -- |
["--port", "8080"] → -- --port 8080 |
env |
environment variables for the Elide process | { "CI": "true" } |
An option value of true emits the bare flag, false omits it, an array repeats the option ({ limit: ["workers=4"] }
→ --limit=workers=4), and anything else becomes --name=value; a one-character name gets a single dash. Workspace
settings supply the defaults (elide.flags, elide.build.options, elide.run.options, elide.test.options,
elide.install.options) and each key of a task definition overrides them — false cancels an inherited option.
Every task reports through the $elide problem matcher, so compiler errors and warnings land in Problems and on
the offending line. It matches the two-line kotlinc shape Elide prints — [212ms] error: kotlinc: <message> followed
by In file: <path>[:<line>[:<col>]] — and resolves the path by searching the workspace folder (skipping .dev,
.git and node_modules), so relative paths reported from a project root in a subdirectory still resolve. javac
diagnostics interleave symbol:/location: lines between the two and are shown in the terminal only.
Run with Elide / Debug with Elide appear above every fun main( (Kotlin, including @JvmStatic fun main in
an object) and public static void main in a production source root of a synced project, and Run / Debug
in elide.pkl on jvm.main, on each entrypoint element, on each scripts entry (Run script — a script is a
shell command line, so there is nothing to attach a debugger to) and on each artifacts entry (Build). A file
whose main class is the manifest's jvm.main runs as elide run; any other file is passed to Elide as the
entrypoint. Detection is regex-based — no Kotlin or Java parser is involved — so exotic declarations are missed; set
elide.codeLens.enabled to false to turn the lenses off.
The JetBrains Kotlin LSP contributes a second, unqualified Run / Debug pair on the same line from its own
LSJvmRunMainCodeLensProvider. It is a server-side feature with no setting, no registry flag and no exported client
API, so this extension cannot suppress it — hence the with Elide suffix. That pair launches the class through the
IntelliJ debug adapter rather than elide run, so it does not build first and does not see the project's compiled
output, which workspace.json does not carry.
JUnit tests in the test source roots of a synced project appear in the Testing view, grouped project → class
→ method, with nested classes nested. Discovery is static: the test sources of the project model are scanned for
@Test (and @ParameterizedTest, @RepeatedTest, @TestFactory, @TestTemplate) declarations, so the tree is
populated without compiling or running anything, and it follows edits — saved or not — through a file watcher and the
open editor's buffer. Like the code lenses, the scan is regex-based, so exotic declarations are missed.
A run executes elide test --reporter=tap in the project root, adding --test-name-pattern=<pattern> whenever the
selection is narrower than the whole project (the JVM engine full-matches that pattern against pkg.Class#method,
with $ separating nested classes). elide.flags and elide.test.options apply here too, except for reporter,
which stays tap because this run parses that stream. Results are reported as the TAP stream settles: a failure's
message and detail block become the test's message, and the first stack frame naming the test's own file
positions it in the editor. A label that matches no discovered item is added under the project item, so a result is
never dropped; a run that reports no result at all (a pattern matching nothing, a compile error) marks the selected
tests errored with the CLI's stderr.
The Debug profile runs the same command with a bare --debugger and attaches elide.debug.adapter once the JDWP
agent announces itself. That flag takes no address on elide test: the agent always binds 5005, so one debug run at
a time.
Launch configuration type elide:
{
"type": "elide",
"request": "launch",
"name": "Elide: Run (debug)",
"entrypoint": "src/main.kt",
"args": ["--port", "8080"],
"flags": ["release"],
"options": { "coverage": true }
}The extension runs elide run --debugger [flags] [options] [entrypoint] [-- args] in a terminal, waits for the JDWP
agent's Listening for transport dt_socket at address: <port> line, then attaches the JetBrains JVM debugger
(elide.debug.adapter: intellij) or Debugger for Java (java, requires vscjava.vscode-java-debug). Stopping the
session terminates the Elide process. The JDWP agent always binds port 5005, so one debug session at a time.
args are the debugged application's own arguments; elideArgs adds further positional arguments to the Elide
command itself. flags, options and env layer over elide.flags and elide.<command>.options exactly as in a
task, and --debugger is always added — set debugger in options to choose its value (dap, cdp, a
host:port address).
command selects what is debugged:
command |
Runs | What it acts on |
|---|---|---|
run (default) |
elide run --debugger |
entrypoint: file, manifest script, or empty for entrypoint/jvm.main |
test |
elide test --debugger |
the whole test run; narrow it with "options": { "test-name-pattern": "MyTest" } |
build |
elide build <targets> --debugger |
targets: build targets (run, jvm-test, … — elide build --inspect lists them) |
On build, --debugger is an option of the target task rather than of the build itself, so the configuration must
name at least one target; one with an empty targets assembles nothing and is rejected before any process starts,
and entrypoint is refused there (it belongs to run). Target options go in options the same way. targets may
list several targets, but only one of them can start a JVM: the bare --debugger binds 5005 for each, so two
debuggable targets in one configuration collide on that port. The Build targets section of the Elide sidebar
offers a Debug action on every target that declares --debugger.
{
"type": "elide",
"request": "launch",
"name": "Elide: Build (debug)",
"command": "build",
"targets": ["jvm-test"],
"options": { "select": "class:com.example.SomeTest" }
}| Setting | Default | Description |
|---|---|---|
elide.home |
"" |
Distribution root containing bin/elide. |
elide.jdk.home |
"" |
JDK for symbol resolution; else jvm.javaHome, $JAVA_HOME, installed JDKs matching jvm.target. |
elide.sync.onStartup |
true |
Sync when the workspace opens. |
elide.sync.onManifestChange |
"prompt" |
always / prompt / never when elide.pkl changes, or when the lockfile's resolved dependencies do. Every elide invocation rewrites .dev/elide.lock*.bin, so rewrites with unchanged content are ignored. |
elide.kotlinLsp.writeWorkspaceJson |
true |
Write <folder>/workspace.json. |
elide.install.classifiers |
["sources"] |
Classifiers installed for declared Maven packages (sources, docs); empty installs classes only. Elide's own Kotlin/JUnit jars have none. |
elide.flags |
[] |
-f NAME[=VALUE] build flags for every invocation, sync included; changing them marks the model stale. |
elide.build.options |
{} |
Default options for elide build, e.g. { "no-cache": true }. |
elide.run.options |
{} |
Default options for elide run. |
elide.test.options |
{} |
Default options for elide test; Test Explorer runs use them too, except reporter. |
elide.install.options |
{} |
Default options for the elide install task. |
elide.codeLens.enabled |
true |
Show the Run/Debug code lenses described above. |
elide.debug.adapter |
"intellij" |
intellij or java. |
packages/core—@elide/ide-core: editor-agnostic library (Elide discovery, CLI runner, manifest decoding, project model,workspace.jsonemitter). No VS Code dependency; reusable by other TypeScript-based editor integrations.packages/vscode— the extension; itsCHANGELOG.mdis the release history shown on the Marketplace.samples/ktjvm— Kotlin/JVM sample used by the integration test.tools/deploy.sh— packaging and publication toplugins.elide.dev.
bun install
bun run build # core (tsc) + extension (esbuild)
bun test # core unit tests
cd packages/vscode && bun run test:integration # drives real VS Code + Kotlin LSP against samples/ktjvmThe integration test requires VS Code at /Applications/Visual Studio Code.app, JetBrains.kotlin-server installed
in ~/.vscode/extensions, elide installed, and network access (it adds Guava to the sample). Press F5 in this repo to
run the extension against samples/ktjvm.
Issues and pull requests are welcome. CONTRIBUTING.md covers the dev loop, the commit convention
(Conventional Commits, enforced on pull requests), and the release process; participation is governed by the
Code of Conduct. Report vulnerabilities privately as described in
SECURITY.md — not in a public issue.
MIT — see LICENSE.
{ "type": "elide", "command": "test", "args": ["src/api"], "options": { "bail": 3, "test-timeout": 5000 }, "programArgs": ["--filter", "slow"] }