Files
l0ng-ai 01fd25ab9b perf(java): keep the configuration snapshot across editor activity
The configuration index was invalidated by every Java element event,
including POST_RECONCILE, and its working-copy fingerprint covered every
open editor. Opening the navigation target, closing a tab, or saving
application.yml was enough to make the next Find Usages or YAML key
navigation rebuild the whole project index.

Invalidation now follows what the index depends on:
- POST_CHANGE deltas only, ignoring working-copy open/close, AST-only
  flags and non-Java resource content on projects, roots and packages
  (configuration files are parsed by the client, metadata files are
  stamp-checked).
- The fingerprint covers working copies with unsaved changes only.

Each snapshot reports a generation so the real-server harness can assert
that opening/closing editors, a full build and a saved edit keep it warm,
while a source added on disk still invalidates it.

Bundle 1.2.1 (installed as java-usages-1.2.1.jar so OSGi does not reuse
the cached 1.2.0), reproducible SHA-256
cbae3e3a0bcf2818b4cc9d98998bd9b81f92e1ae7803dc757134485f9776a4e1.
2026-11-03 17:37:35 +08:00
..
2026-10-22 18:55:10 +08:00
2026-10-22 18:55:10 +08:00
2026-10-22 18:55:10 +08:00
2026-10-22 18:55:10 +08:00

Writing OpenIDEA plugins

OpenIDEA plugins are sandboxed WebAssembly components (wasmtime, component model, WASI preview 2), in the style of Zed extensions. A plugin can register commands (they become IDE actions you can search for and bind to keys), read project files, insert text into the editor, show notifications and contribute themes and languages. Plugins that only contribute themes or languages need no code at all.

plugins/
├─ build.sh                 # builds Rust plugins into components
├─ hello/                   # Rust plugin: two commands
└─ language-toy/            # manifest-only: a language definition

The API is defined in wit/plugin.wit (openidea:plugin/plugin@0.1.0).

1. The package

A plugin is a directory with a plugin.toml:

id = "hello"                    # [a-z0-9][a-z0-9_-]*, unique
name = "Hello"
version = "0.1.0"               # semver
description = "Inserts today's date and counts words."
authors = ["You <you@example.com>"]
homepage = "https://example.com" # optional
openidea-api = "0.1"                # API version this plugin targets
wasm = "dist/plugin.wasm"       # omit for contribution-only plugins
permissions = ["project-read", "editor-read", "editor-write"]

# Optional static contributions (active even before / without code):
[[contributes.commands]]
id = "insert-date"              # [a-z0-9][a-z0-9._-]*
title = "Hello: Insert Date"
category = "Hello"              # group in Search Everywhere / Keymap

[[contributes.themes]]
path = "themes/my-theme.json"

[[contributes.languages]]
path = "languages/my-lang.toml"

All paths are relative to the package directory and may not leave it.

Permissions

Permission Unlocks
project-read workspace-root, read-file (project root only)
editor-read active-file
editor-write insert-text, replace-selection

log, register-command, show-notification, register-language and register-theme are always available. Calling a gated function without its permission traps: the plugin is stopped and the user is told which permission is missing.

2. Writing code (Rust)

wasm32-wasip2 produces a component directly, so all you need is rustup:

rustup target add wasm32-wasip2

Cargo.toml:

[package]
name = "my-plugin"
version = "0.1.0"
edition = "2024"

[workspace]              # keep it out of any parent workspace

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.63"

[profile.release]
opt-level = "s"
lto = true
strip = true
panic = "abort"

src/lib.rs:

wit_bindgen::generate!({ world: "plugin", path: "path/to/openidea/wit" });

use openidea::plugin::host::{self, LogLevel, NotificationLevel};

struct MyPlugin;

impl Guest for MyPlugin {
    fn activate(_ctx: Context) -> Result<(), String> {
        host::register_command("shout", "My Plugin: Shout Selection", Some("My Plugin"));
        Ok(())
    }

    fn deactivate() {}

    fn run_command(id: String, arg: Option<String>) -> Result<Option<String>, String> {
        match id.as_str() {
            "shout" => {
                host::replace_selection(&arg.unwrap_or_default().to_uppercase());
                Ok(None)
            }
            _ => Err(format!("unknown command {id}")),
        }
    }
}

export!(MyPlugin);

Build (copy build.sh or run it with your plugin's path):

plugins/build.sh hello                 # → plugins/hello/dist/plugin.wasm
plugins/build.sh /path/to/my-plugin

Any language with component-model tooling works (e.g. componentize-py, jco componentize, TinyGo): the only contract is the plugin world.

Host functions

Function Notes
log(level, msg) Goes to the IDE log, tagged with the plugin id. eprintln! output is logged too and shown when the plugin crashes.
register-command(id, title, category?) Becomes action plugin.<plugin-id>.<id>; call again to rename. Max 256 commands.
show-notification(level, title, body) Balloon in the invoking window.
workspace-root() Root of the project the command runs in.
active-file() Absolute path of the focused editor's file.
read-file(path) Absolute or project-relative; ../symlinks escaping the root are rejected; UTF-8 only, ≤ 16 MiB. Unsaved editor contents win over disk.
insert-text(text) Inserts at every cursor of the active editor (cursor ends after the text).
replace-selection(text) Replaces every selection and selects the new text.
register-language(toml) Same format as languages/<id>.toml.
register-theme(json) See Themes below.

run-command receives an optional string argument: executeAction("plugin.hello.insert-date", "arg") from another feature, or nothing when run from Search Everywhere / a shortcut. Returning Err(msg) shows "