Files
l0ng-ai 78e0c7a23e feat(plugins): finalize plugin API before release, concurrent providers
Plugin API (breaking; the plugin system is unreleased):
- Remove the runtime `contributions` interface (register-command,
  register-language, register-theme). Commands, languages and themes are
  declared in plugin.toml only, so everything a plugin contributes goes
  through the install preview and consent. The registry drops runtime
  scope, `contribute` and `dispose_runtime`; inline `definition` in
  language and theme declarations is gone.
- `completion-item.kind` is a string (the 25 LSP kinds; unknown kinds show
  no icon), like the other open-ended kinds.
- `lifecycle.activate` and `commands.run` return `types.error` instead of a
  string. The host maps it to the matching error kind, and only `failed`
  counts against the plugin: cancelling, a missing grant or an unknown
  command does not. The SDK's `Plugin` returns `Result<_, Error>`; plain
  messages convert to `Error::Failed`.

Runtime:
- Editor providers of several plugins are called at the same time
  (`Actor::submit` + `Pending::wait`): the wait is the slowest plugin's,
  not the sum, so each gets the full edit-path budget.

Versioning and tooling:
- Components are checked at install and by `openidea-plugin check` for the
  plugin API they were built for; another major or a newer minor is
  refused with what to do.
- Tests: the SDK's WIT matches the host's, a simulated next-minor host
  links every shipped component, every host function has an SDK wrapper,
  released APIs only grow (no snapshot until the first release).
- plugins/build.sh builds every checked-in component, records a source
  fingerprint next to it, and `--check` fails on stale components; CI runs
  it plus fmt, clippy (host and wasm) and tests for the SDK, the official
  plugins and the fixtures.
- The API evolution rules and the checklist for a new host function are in
  plugins/README.md; the design notes under docs/ are removed.
2026-11-04 02:41:46 +08:00

147 lines
4.7 KiB
Plaintext

package openidea:plugin@1.0.0;
/// Declarative views: tool windows declared
/// under `[[contributes.views]]` and custom editors under
/// `[[contributes.customEditors]]`. The plugin describes what to show; the
/// IDE renders it with its own components (theme, keyboard, accessibility)
/// and sends the user's actions back as `view-event`s. Nothing of the plugin
/// runs in the WebView, except a `html` body, which is shown in a sandboxed
/// frame that can only exchange messages with the plugin.
interface view-types {
record button {
id: string,
label: string,
/// An IDE icon name (`refresh`, `add`, `remove`, `run`, …).
icon: option<string>,
enabled: bool,
}
/// A tree node; `parent` is the key of an earlier item (none: a root).
record tree-item {
key: string,
parent: option<string>,
label: string,
icon: option<string>,
/// Grey text after the label.
detail: option<string>,
/// Shows an expander even before its children are known
/// (`expand` asks for them).
expandable: bool,
expanded: bool,
}
record list-item {
key: string,
label: string,
icon: option<string>,
detail: option<string>,
}
variant field-value {
text(string),
multiline(string),
checkbox(bool),
/// The options and the chosen one.
choice(tuple<list<string>, string>),
}
record field {
key: string,
label: string,
value: field-value,
}
/// Rows `first-row`..`first-row + len(rows)` of `total-rows`: the IDE
/// asks for the rows it shows (`viewport`), so large tables stay cheap.
record table {
columns: list<string>,
total-rows: u32,
first-row: u32,
rows: list<list<string>>,
editable: bool,
}
variant body {
/// A centred message (empty state, errors).
message(string),
tree(list<tree-item>),
%list(list<list-item>),
form(list<field>),
table(table),
/// HTML shown in a sandboxed frame: no scripts from elsewhere, no
/// network, no IDE API. Its script talks to the plugin with
/// `openidea.postMessage(text)` / `openidea.onMessage(fn)`.
html(string),
}
record view {
toolbar: list<button>,
body: body,
/// A line below the body (counts, last refresh…).
status: option<string>,
}
/// The table rows the IDE shows.
record viewport {
first-row: u32,
rows: u32,
}
record cell-edit {
row: u32,
column: u32,
value: string,
}
variant view-event {
/// A toolbar button.
button(string),
/// Double click / Enter on a tree or list item.
activate(string),
select(string),
expand(string),
collapse(string),
/// A form field changed: key and new value (`true`/`false` for checkboxes).
field(tuple<string, string>),
cell(cell-edit),
/// Insert an empty row before this one (the row count: append).
insert-row(u32),
delete-rows(list<u32>),
/// From the `html` body's script.
message(string),
}
}
/// What a plugin's code can ask of its views.
interface views {
use types.{error};
/// The view (or every custom editor of that id) shows outdated content:
/// the IDE renders it again.
refresh: func(view: string);
/// Sends a message to the script of a view's `html` body.
post-message: func(view: string, message: string) -> result<_, error>;
/// Opens a terminal tab in the window the call came from, running
/// `program` (pinned by a `process:<program>` grant) with `args` in the
/// project root: long-running output (logs) and interactive sessions.
open-terminal: func(title: string, program: string, args: list<string>) -> result<_, error>;
}
/// The plugin's own settings (`[contributes.settings]`), as the user set them
/// (project over global) or the schema's default.
interface settings {
/// The value of `key` as JSON (`true`, `3`, `"text"`), none if unknown.
get: func(key: string) -> option<string>;
}
/// Optional export (world `plugin-views`): content of declared views and
/// custom editors. For a custom editor, `ctx.editor()` is its document.
interface view-provider {
use view-types.{view, viewport, view-event};
use invocation.{invocation};
use types.{error};
render: func(view: string, viewport: option<viewport>, ctx: borrow<invocation>) -> result<view, error>;
handle: func(view: string, event: view-event, ctx: borrow<invocation>) -> result<_, error>;
}