Skip to main content
A plugin can put UI in several places: Gutter markers and gutter buttons are editor providers: see Language features. File icons are on Icon themes.

Declare a tool window

A view without web needs code (wasm). Showing a view activates the plugin (on-view:<id>).

Grouped and conditional views

The first declaration of a group supplies the window’s title, icon, location and secondary, even when that view is hidden; the window disappears when all its tabs are hidden. Tabs load on first selection and stay mounted while they apply, so web views keep their state when you switch tabs. when is evaluated again on context changes, file creation and removal, and project changes. These fields only control visibility; they grant no access.

Render a view

Register register_plugin!(MyPlugin, views) and implement openidea_plugin::views::Views (WIT export view-provider). A view is data: a toolbar, a body and a status line. The IDE draws it with its own components (theme, keyboard, speed search) and sends back what the user does. From the TODO Finder example:
When content changes, call host::refresh_view(view) and the IDE calls render again. host::reveal_view(view) brings one of the plugin’s views to the front in the invoking window. For a tool window, ctx.editor() is the window’s active text editor, so toolbar buttons can act on what the user is editing.

Bodies

Toolbar buttons: View::new(body).button(Button::new("refresh", "Refresh").icon("refresh")) and .status("12 items").

Events

ViewEvent carries Button(id), Activate(key) (double click or Enter), Select(key), Expand(key), Collapse(key), Field(key, value) (true/false for checkboxes), Cell { row, column, value }, InsertRow(before), DeleteRows(rows) and Message(text) from an HTML body. Table row clicks and Space decode with event.table_select(); double click and Enter with event.table_activate() (API 1.3, absolute 0-based rows). Bodies, fields and events added by later APIs travel as Other { kind, data }: an IDE that doesn’t know a body says it needs updating, and leaves out a field type it doesn’t know. Item right-click menus use viewItem.context (see Commands and actions).

Custom editors

A custom editor opens files matching its patterns and edits the file’s text document:
  • priority = "default" opens matching files in the plugin’s editor; option makes it available through Open With. Open as Text opens the text editor.
  • layout = "split" or "preview" opens the file in the text editor with the plugin’s editor beside it and the Editor Only / Editor and Preview / Preview Only switch in the tab strip, as for Markdown; the value is the layout files start in. "tab" (default) is a tab of its own.
  • In render and handle, ctx.editor() is the editor’s document. Read it with document() and change it with apply_edits(version, edits). The text editor of the same file follows, Undo/Redo is one history with it, and Save, the dirty mark and the close prompts are the document’s.
CSV Editor turns table edits into text edits:
It needs editor.read and editor.write.

HTML bodies

Body::Html runs in a sandboxed frame: an opaque origin under a CSP that allows only its inline scripts and styles and data: images, with no network and no IDE API. It talks to the plugin only through openidea.postMessage(text) and openidea.onMessage(fn), which arrive as ViewEvent::Message and are answered with host::post_view_message(view, text). Regex Tester works this way. Rendering again reloads the frame, so let the page keep its own state. The frame follows the IDE theme without reloading. Its root carries data-theme="light|dark" and these CSS variables with concrete colour values: Scripts also get openidea.theme ({ dark, vars }) and openidea.onThemeChange(fn). Variables are only ever added.

Web views

A view or custom editor can be a web page shipped in the package, for UIs the declarative bodies can’t express (a canvas, a diagram editor). It needs no code:
  • The page’s directory (never the package root) is part of the declaration: every file under it is checked (at most 4096 files and 256 MiB, no links), installed and signed with the package.
  • The page loads into an <iframe sandbox="allow-scripts"> from the IDE’s own URL scheme: opaque origin, no network, no cookies or storage, only the page’s own files. Build it with relative paths (Vite: base: "./").
  • The same openidea object as in HTML bodies is available before the page’s scripts run, plus:
IDE shortcuts the page doesn’t handle (Save, Close, Find Action…) still work while it has focus; editing keys stay with the page. With the clipboard capability the page may use navigator.clipboard. Custom header (API 1.3). An ungrouped tool-window web view can declare header = "none": once the page is ready, the IDE replaces the tool-window header with corner actions (Options, Hide) and sets --openidea-corner-inset-width (64px) and --openidea-corner-inset-height (34px) so the page can pad its own top bar:
Graphviz is a complete example: a Vite + React page in web-src/, built into web/ by plugins/build.sh official/graphviz, whose code publishes the errors Graphviz reports as problems.

Page protocols (API 1.3)

With engines.openidea = "^1.3", a page can send JSON messages through openidea.postMessage to ask the IDE for more. All of them run only programs granted with process:<program>, in the window’s trusted project root, never through a shell: The message fields and limits are in the plugin guide; the built fixtures web-pty, web-log and web-content under crates/ide-plugin/tests/fixtures/ exercise them.

Services views and detail panes

location = "services" (API 1.3) puts a view’s tree in the Services tool window next to Docker and the run configurations, instead of a tool window of its own:
The tree’s items are Services nodes: render, handle, the toolbar (acting on the selected node) and viewItem.context menus work as in a tool window. The pane beside the tree comes from the detail-provider export: register_plugin!(MyPlugin, views, details) and implement openidea_plugin::details::Details:
A node has at most 8 tabs; the IDE switches them without calling the plugin and asks again after host::refresh_view(view). A tab is a Pane::Message, a read-only Pane::Text (at most 1 MiB) or a ProcessLog: the IDE runs that program (it needs the process:<program> grant) in the project root and follows its output (ANSI stripped, the last 5000 lines, Soft-wrap, Clear, the exit code at the end). A toggle appends its args and restarts the program. It stops when the pane closes or Services is hidden. List and detail views (API 1.3). A tree or list view in any location can declare detail = true to show the same Details tabs beside the selected item, with a resizable divider. The host asks after the item’s select event and again after refresh_view.

Tabs and terminals opened by code

These are called from a command or event of a window:

Decorations

Decorations (API 1.4, WIT decorations.wit) annotate editors and the Project tree. They need project.read; paths must be inside an open project.
  • Styles map to theme tokens: highlight, search, added, modified, removed, warning, error, info, underline, strikethrough, muted. Unknown styles are ignored.
  • A set (set_id) belongs to the plugin and project and is shared by all windows of that project. Setting replaces the set’s items for that file; clear_decorations(set_id) and clear_file_decorations(set_id) remove a set everywhere.
  • Limits: 50 editor sets and 50 tree sets per plugin, 10,000 editor items per file, 4 MiB of metadata. Disabling, unloading or a crash withdraws them; idle reclaim keeps them.
Blame Annotations turns Project::vcs_blame into end-of-line decorations.