plugins/README.md.
Three crates
Dependency direction:
ide-core ← ide-extension ← feature crates (their extension points) and ide-plugin ← ide-host (assembly). src-tauri only adapts PluginHost to the plugin_* Tauri commands.
The plugin host runs where the project is: in the desktop app for local projects, in openidea-server for remote ones.
The interface
The API is defined in WIT, inwit/ (package openidea:plugin@1.5.0). A plugin is a component targeting the plugin world (or plugin-syntax). The guest SDK sdk/rust/openidea-plugin wraps it in a typed Rust API and carries a copy of wit/; crates/ide-plugin/tests/api_compat.rs checks the copies are identical.
Released API versions are frozen. wasmtime resolves interface names by semver, but function types must match exactly, so a released 1.x only grows: new interfaces and new optional exports, never changed records or variants. See Evolving the plugin API in plugins/README.md before touching wit/.
Package lifecycle
Sources of packages: installed plugins (<plugins_dir>/<id>/), project plugins (<project>/.openidea/plugins/*/, loaded only for trusted projects), and folders linked with openidea-plugin link (links.json).
1
Prepare
The manifest is parsed and every declaration is validated by the extension point that owns it
(
ExtensionRegistry::prepare). No side effects.2
Consent
The grant ledger decides whether the package may load. New plugins and project plugins wait for the user’s consent;
an update that asks for a new capability must be confirmed again.
3
Commit
Contributions take effect all or nothing (
ExtensionRegistry::commit) and can be withdrawn per plugin.4
Activate
Code is instantiated only when one of its activation events fires:
on-command, on-language, on-file,
on-view, on-schedule, on-event, or on-startup-finished.Extension points
An extension point is implemented by the crate that owns the feature, so the kernel doesn’t grow with every contribution type. For exampleide-language implements the languages point (plugin_point.rs), and ide-host::plugins implements providers, run detectors, themes and navigation indexers. UI points (ui.rs in ide-extension, ui_points.rs in the host) validate menus, keymaps, status bar items, settings, views and custom editors completely in Rust.
The runtime
One actor per plugin
Each active plugin’s runtime lives on its ownActor thread (crates/ide-plugin/src/actor.rs). Callers queue jobs and wait on a reply channel instead of holding a lock across the call, which gives three properties:
- an instance is entered by one job at a time, never concurrently;
- an edit-path caller (completion, line markers) can stop waiting at its soft deadline while the call runs on, and is interrupted at the hard deadline;
- any thread can cancel a call.
Sandbox
Each plugin gets its own wasmtimeStore: no preopened directories, no environment, no network, no inherited stdio — only clocks and randomness from WASI, plus the host functions. Every host function that reaches files, processes, the network or VCS writes checks a capability the user granted; a missing grant returns denied and the plugin keeps running. Path grants are resolved to real paths before comparison, and process: grants pin the program’s absolute path and hash.
Limits
Defaults fromLimits in crates/ide-plugin/src/types.rs:
Deadlines and cancellation are enforced by wasmtime epoch interruption: the store’s epoch callback checks the per-call deadline and the cancel flag. A trap (panic, fuel or memory exhausted, timeout, permission violation, cancellation) poisons the instance; the actor drops it and the host’s supervisor starts a fresh one the next time it is needed. A call that returns normally resets the crash count. Settings › Plugins shows activations, p95 call latency, crashes, late editor results and reclaims.
How contributions reach the UI
- Declarative UI is validated completely in Rust and committed as JSON into the
UiStore. The client receives the store’s snapshot and projects each item onto an existing workbenchregister*function (src/features/plugins/projection.ts). A projection must not fail; if it throws, that item is logged and skipped, nothing is half-registered. Large payloads such as icon SVGs go into the store’s assets and are fetched on demand. - Runtime UI from running code (status item text, notifications, tasks, event subscriptions) arrives as
PluginEvents on theplugin_subscribechannel and is handled byruntimeUi.tsxandbridge.ts. - Views and custom editors whose content plugin code renders are served by the
plugin_view_render/plugin_view_eventcommands. - Web views are pages shipped in the package. The client loads them in a sandboxed frame (an opaque origin with no network and no IDE API) from the
openidea-pluginURL scheme (src-tauri/src/plugin_web.rs), with a CSP of their own; the page talks to the IDE only through the frame bridge.
defineUiPoint in the client; the kernel (projection, store, bridge) isn’t touched.
Remote projects
When a window opens a remote project, the desktop sends its enabled code plugins and their grants toopenidea-server, which runs them next to the files: file reads, process starts, fetches and diagnostics act on the remote machine, and process: programs are resolved and pinned there. Changes on the desktop (install, disable, grants) follow within a second. Secrets stay in the desktop’s keychain; the server asks for one with the plugin_secret request. Project plugins don’t run in remote projects yet.
Testing the host
crates/ide-host/tests/plugins.rs and the other plugin_*.rs files) load the checked-in components of the official plugins and the fixtures in crates/ide-plugin/tests/fixtures/. Rebuild them after changing a plugin, the SDK or wit/. pnpm perf:rust includes plugin runtime benchmarks (plugins/load_50, plugins/cold_activation, plugins/syntax_highlights_2k_lines).