- A language, declaratively.
[[contributes.languages]]adds file detection, comments, indentation, language servers and a debugger, with no code.[[contributes.snippets]]adds live templates. - Syntax for a language the IDE has no grammar for. The optional
syntax-providerexport returns highlighting, structure and folds. - Editor providers for any language, built-in or not: completion, hover, go to
definition, gutter markers, semantic tokens, inlay hints, formatting, code lenses and
the rest. Declare them under
[[contributes.providers]].
Declare providers
activation entry is needed. The host calls only
the kinds you declare. Providers need code (wasm) but no extra capability: the document
comes with the call. Host functions you call from a provider keep their usual capability
checks.
Combine exports in one call:
register_plugin!(MyPlugin, providers, hover, language_features).
Every trait method has a default that returns nothing, so implement only what you declared.
A minimal provider
Document with the absolute path, the editor language id, a
version and the full text, unsaved edits included. Positions are 0-based lines and
UTF-16 columns; openidea_plugin::text::offset and text::position convert between them
and byte offsets.
Budgets
Editor providers run on the editing path:- A result later than about 50 ms is dropped; a call still running at 500 ms is interrupted. Every plugin providing a kind is asked at the same time, so each has the whole budget.
- Providers share the plugin’s single instance with its commands, events and views. They see the same memory but also wait behind them: a provider call queued behind a long command is dropped at 50 ms like a slow one. Keep commands and event handlers short.
inline-completionis exempt from the 50 ms (it may need the network) but is cancelled as soon as the user types on. CheckTask::is_cancelledor let the host interrupt it.formattingand code-action resolution use the 5-second command budget.
Completion, inline completion, navigation and gutter markers
These are methods ofproviders::Providers (WIT providers):
completion(doc, at, trigger)returnsCompletionItems that join the language servers’ list.kind(CompletionKind) picks the icon. An item can be asnippet($1,${1:name},$0), replace areplacerange, match onfilter_textinstead of its label, sort bysort_text, carrydocumentation, and bringadditional_editselsewhere in the document (an import).inline_completion(doc, at)returns ghost text at the caret (InlineSuggestion { insert_text, replace }). AI Completion is a working example.definition(doc, at)returnsLocations, merged with the language server’s for Go to Declaration (⌘B/Ctrl+B).line_markers(doc)returns gutter icons with atooltipand an optionaltargetopened on click.
definition, line-markers and completion for Java and XML.
Marker icons. icon accepts an icon name (including registered plugin icons), the
aliases navigate (default), implemented, implementing, overridden, overriding
and run, or a package SVG declared as a marker icon (API 1.3):
icon: Some("icons/resource.svg".into()) to use it. Marker SVGs are at most
64 KiB and sanitised like view icons; unknown icons keep the navigation glyph. For a
clickable command instead of navigation, use gutter-actions (below).
Semantic tokens
semantic-tokens (API 1.3) colours tokens in documents of the declared languages,
including languages the plugin doesn’t contribute, such as php:
Hover
hover (API 1.3) adds Markdown to the hover. The host asks the language server and the
plugins together and shows the server’s hover first, then each plugin’s below a rule.
Hover::new(markdown).range(range) names the text the hover describes (used when the
server names none). At most 64 KiB of Markdown per plugin.
Inlay hints
inlay-hints (API 1.3) shows short labels inside the code:
range is the visible part of the document; hints outside it are dropped. kind
(Type, Parameter or Other) decides which Inlay Hints setting shows the hint.
.padding_left() and .padding_right() add spaces. Labels are cut at 256 bytes, tooltips
at 1 KiB, and at most 10,000 hints per call.
More language features (API 1.4)
Declare any of these kinds withengines.openidea = "^1.4", implement
language_features::LanguageFeatures and register language_features:
A code lens that offers a command, from the
Blame Annotations
example:
gutter-actions. Rules that apply to all of them:
- A missing or unavailable language server lets plugin results through; other server errors stay errors.
document-symbol.parentis an index into the returned flat list. A parent must come first and contain its children; invalid parents become roots, depth is capped at 64.- Each list holds at most 10,000 items, text at most 64 KiB per string; invalid ranges are dropped. Format edits must be valid and must not overlap.
- Disabling the selected formatter reports it as unavailable rather than silently using another one.
Embedded languages (injections)
injections (API 1.4) marks ranges of a host document that contain another language,
such as SQL inside string literals. Declare the kind for the host language,
implement Injections and register injections:
- Highlights each range with the embedded language’s parser and the current colour scheme, refreshed 300 ms after edits.
- Routes completion, hover, diagnostics and the API 1.4 features to the embedded language’s plugin providers, through a virtual document that keeps the original path and positions and blanks out text outside the ranges.
- When the embedded language has a configured language server, routes completion, hover, signature help, navigation, references, highlights and code actions to it, each range in its own in-memory sibling document (never written to disk). Formatting and rename stay with the host language’s server.
Syntax for a plugin language
For a language the plugin contributes and the IDE has no tree-sitter grammar for, exportsyntax-provider (world plugin-syntax). Offsets here are UTF-8 byte ranges into the
text; the IDE converts them.
activation = ["on-language:<id>"] so the component
loads with the first file. Results are validated: clamped to the text, overlaps dropped.
Solidity is the
complete example.
Contributing a language
languages/<id>.toml in the repository):
- Plugin languages join the registry like built-in ones: detection (
extensions,filenames,first-line), comments, indentation,ui-mode,framework-ui-modes, and thelanguage-serversanddebuggerthey declare. - Each language server or debugger command becomes an optional
process:<program>capability. Refusing it keeps the rest of the plugin working. - A plugin can’t replace a built-in or user definition: their ids, extensions and file names win. Users can still map files to a plugin language in Settings › File Types.
- In a remote project the definitions go to the server, which starts the servers there.
openidea-plugin new my-lang --template language starts a data-only language plugin.
Snippets
prefix and body take a string or an array of strings (body lines join with newlines).
$1, ${1:default} and a final $0 become linked live-template fields; \$ is a literal
dollar. Snippets show in completion, expand with Tab, and are listed read-only under
Settings › Editor › Live Templates, grouped by plugin. No code or capability is needed.
Limits: 1 MiB per file, 1,000 entries, 32 prefixes per entry, 64 KiB per body.
Testing
On your machine the SDK calls its mock host, so call your trait methods directly incargo test. MockHost::provider_document() returns the current unsaved snapshot of the
mock editor. See Testing.