дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
A plugin system built on WebAssembly components (wasmtime, WASI p2), with a typed WIT API (`openidea:[email protected]`) and a Rust SDK, shipped as a preview. - Extension points declared in `plugin.toml` (commands, languages, themes, icon themes, menus, key bindings, status items, settings, views, custom editors, file icons and templates, run detectors, editor providers), validated before anything takes effect and projected onto the workbench's own registries: plugin themes and file icon themes are chosen in Settings › Appearance and Quick Switch, plugin shortcuts are edited in Settings › Keymap, plugin problems are grouped in Settings › Inspections. - Capabilities with risk levels and consent at install, pinned `process:` programs, secrets in the OS keychain. - Runtime: one thread per active plugin, fuel/time/memory budgets, soft and hard deadlines on the editing path, cancellation, crash backoff, idle reclaim, a precompiled-component cache; remote projects run code plugins on `openidea-server`. - Web views and custom editors with a sandboxed page, a document bridge joined to the text editor's undo history, and split or preview layouts. - Distribution: signed `.oiplugin` packages, a static marketplace index with revocation, versioned installs with rollback, install from a `.zip` or a folder. The release workflow signs and publishes the official plugins and their index, which released builds use as the default marketplace. - Official plugins: String Tools, Key Promoter, Git Toolbox, AI Completion, Checkstyle, MyBatis Navigator, CSV Editor, Solidity, Material Icons, Harbor Night, Excalidraw, Graphviz, Regex Tester, License Header. Closes #732 Co-authored-by: Deng Liu <[email protected]> Co-authored-by: florentvdry <[email protected]>
104 рядки
4.1 KiB
TypeScript
104 рядки
4.1 KiB
TypeScript
/**
|
||
* Plugins: Settings › Plugins (installed list, enable/disable, install from
|
||
* disk with a permission page, uninstall, theme selection) and the runtime
|
||
* bridge to the plugin host (`ide_host::plugins`): UI contributions are
|
||
* projected by `projection.ts` / `points.tsx`.
|
||
*
|
||
* Actions: `plugins.settings`, `plugins.installFromDisk`, `plugins.reloadAll`,
|
||
* plus one action per plugin command: `plugin.<pluginId>.<commandId>`
|
||
* (searchable in Search Everywhere › Actions and bindable in the keymap editor;
|
||
* a string arg is forwarded to the plugin).
|
||
*
|
||
* Editor contribution `plugins.lineMarkers`: gutter icons from plugin
|
||
* `line-markers` providers (`lineMarkerGutter.ts`).
|
||
*/
|
||
import { registerEditorContribution } from "@/editor/api";
|
||
import { lazyComponent } from "@/ui";
|
||
import { executeAction, registerActions } from "@/platform/actions";
|
||
import { useProject } from "@/platform/project";
|
||
import { editorService, registerOverlay, registerSettingsPage } from "@/workbench/contributions";
|
||
import { followPluginChanges, startPluginBridge, syncProjection, trackPluginTheme } from "./bridge";
|
||
import { ConsentDialogHost, provideConsentDialog } from "./consent";
|
||
import { trackIconTheme } from "./iconThemes";
|
||
import { pluginLineMarkerGutter } from "./lineMarkerGutter";
|
||
import { defineBuiltinUiPoints } from "./points";
|
||
import { startViewUi } from "./uiPoints";
|
||
import { installFromDisk, refreshPlugins, reloadPlugin, SETTINGS_PAGE_ID, usePlugins } from "./store";
|
||
|
||
function registerPluginsPage(): () => void {
|
||
return registerSettingsPage({
|
||
id: SETTINGS_PAGE_ID,
|
||
title: "Plugins",
|
||
// After Editor, as in IntelliJ IDEA.
|
||
order: 250,
|
||
keywords: ["plugin", "extension", "wasm", "install", "theme", "marketplace"],
|
||
component: lazyComponent(() => import("./PluginsPage").then((m) => m.PluginsPage)),
|
||
});
|
||
}
|
||
|
||
/** Settings › Plugins for the Settings window: the page plus the plugin theme. */
|
||
export function activateSettingsPages(): () => void {
|
||
const disposers = [
|
||
registerPluginsPage(),
|
||
registerOverlay({ id: "plugins.consent", component: ConsentDialogHost }),
|
||
provideConsentDialog(),
|
||
defineBuiltinUiPoints(),
|
||
followPluginChanges(),
|
||
syncProjection(),
|
||
trackPluginTheme(),
|
||
trackIconTheme(),
|
||
];
|
||
void refreshPlugins();
|
||
return () => disposers.reverse().forEach((d) => d());
|
||
}
|
||
|
||
export function activate(): () => void {
|
||
const disposers = [
|
||
registerPluginsPage(),
|
||
registerOverlay({ id: "plugins.consent", component: ConsentDialogHost }),
|
||
provideConsentDialog(),
|
||
registerActions([
|
||
{
|
||
id: "plugins.settings",
|
||
title: "Plugins…",
|
||
category: "Plugins",
|
||
icon: "plugin",
|
||
keywords: ["extensions", "manage plugins"],
|
||
run: () => void executeAction("file.settings", SETTINGS_PAGE_ID),
|
||
},
|
||
{
|
||
id: "plugins.installFromDisk",
|
||
title: "Install Plugin from Disk…",
|
||
category: "Plugins",
|
||
icon: "plugin",
|
||
keywords: ["extension", "wasm", "plugin.toml"],
|
||
run: async ({ arg }) => {
|
||
await installFromDisk(typeof arg === "string" ? arg : undefined);
|
||
},
|
||
},
|
||
{
|
||
id: "plugins.reloadAll",
|
||
title: "Reload Plugins",
|
||
category: "Plugins",
|
||
icon: "refresh",
|
||
keywords: ["dev plugin", "rebuild"],
|
||
run: async () => {
|
||
const ids = usePlugins.getState().plugins.map((p) => p.id);
|
||
for (const id of ids) await reloadPlugin(id);
|
||
},
|
||
},
|
||
]),
|
||
startPluginBridge(),
|
||
// Plugin custom editors (tab kind) and context menu entries.
|
||
startViewUi((input) => void editorService().openInput(input)),
|
||
// Gutter icons from plugins' `line-markers` providers (next to the LSP ones).
|
||
registerEditorContribution({ id: "plugins.lineMarkers", order: 57, extensions: pluginLineMarkerGutter }),
|
||
// Dev plugins (`<project>/.openidea/plugins`) depend on the open project.
|
||
useProject.subscribe((s, prev) => {
|
||
if (s.project?.root !== prev.project?.root || s.project?.trusted !== prev.project?.trusted) void refreshPlugins();
|
||
}),
|
||
];
|
||
void refreshPlugins();
|
||
return () => disposers.reverse().forEach((d) => d());
|
||
}
|