plugins/README.md。
三个 crate
依赖方向:
ide-core ← ide-extension ← 各功能 crate(各自的扩展点)和 ide-plugin ← ide-host(组装)。src-tauri 只把 PluginHost 适配成 plugin_* 系列 Tauri 命令。
插件宿主运行在项目所在的地方:本地项目在桌面应用里,远程项目在 openidea-server 里。
接口
API 用 WIT 定义,位于wit/(包名 openidea:plugin@1.5.0)。插件是面向 plugin world(或 plugin-syntax)的组件。插件端 SDK sdk/rust/openidea-plugin 把它封装成类型化的 Rust API,并带有一份 wit/ 副本;crates/ide-plugin/tests/api_compat.rs 检查两份是否一致。
已发布的 API 版本是冻结的。wasmtime 按 semver 解析接口名,但函数类型必须完全一致,所以已发布的 1.x 只能增加:新的接口和新的可选导出,不能修改已有的 record 或 variant。改 wit/ 之前请先读 plugins/README.md 里的 Evolving the plugin API。
插件包的生命周期
插件包的来源:已安装插件(<plugins_dir>/<id>/)、项目插件(<project>/.openidea/plugins/*/,只对受信任项目加载)、用 openidea-plugin link 链接的目录(links.json)。
1
准备
解析清单,每条声明由负责它的扩展点校验(
ExtensionRegistry::prepare),没有副作用。2
授权
授权账本决定插件包能否加载。新插件和项目插件要等用户确认;更新若要求新的 capability,需要重新确认。
3
提交
贡献要么全部生效、要么全部不生效(
ExtensionRegistry::commit),并且可以按插件撤回。4
激活
只有当某个激活事件触发时才实例化代码:
on-command、on-language、on-file、on-view、on-schedule、on-event
或 on-startup-finished。扩展点
扩展点由负责该功能的 crate 实现,内核不会随贡献类型的增多而膨胀。例如ide-language 实现语言扩展点(plugin_point.rs),ide-host::plugins 实现 provider、运行检测器、主题和导航索引器。UI 扩展点(ide-extension 的 ui.rs、宿主的 ui_points.rs)在 Rust 里完整校验菜单、keymap、状态栏项、设置、视图和自定义编辑器。
运行时
每个插件一个 actor
每个活跃插件的运行时都在自己的Actor 线程上(crates/ide-plugin/src/actor.rs)。调用方把任务排进队列,在回复 channel 上等待,而不是在调用期间一直持有锁。由此得到三个性质:
- 一个实例同一时间只执行一个任务,从不并发;
- 编辑路径上的调用方(补全、行标记)可以在软时限到达时停止等待,调用本身继续执行,到硬时限时被中断;
- 任何线程都能取消一次调用。
沙箱
每个插件有自己的 wasmtimeStore:没有预打开的目录、没有环境变量、没有网络、不继承标准输入输出,只有 WASI 的时钟和随机数,加上宿主函数。每个涉及文件、进程、网络或 VCS 写入的宿主函数都会检查用户是否授予了相应 capability;未授予时返回 denied,插件继续运行。路径类授权在比较前解析为真实路径,process: 授权会固定程序的绝对路径和哈希。
限制
默认值来自crates/ide-plugin/src/types.rs 中的 Limits:
时限和取消通过 wasmtime 的 epoch interruption 实现:store 的 epoch 回调检查本次调用的截止时间和取消标记。trap(panic、fuel 或内存耗尽、超时、权限违规、取消)会使实例失效;actor 丢弃它,宿主的 supervisor 在下次需要时启动一个新实例。一次正常返回的调用会把崩溃计数清零。设置 › 插件 页面会显示激活次数、调用延迟 p95、崩溃、迟到的编辑器结果和回收次数。
贡献如何到达 UI
- 声明式 UI 完全在 Rust 里校验,以 JSON 形式提交进
UiStore。客户端拿到 store 的快照,把每一项投射到工作台已有的register*函数上(src/features/plugins/projection.ts)。投射不允许失败;如果抛错,该项会被记录日志并跳过,不会出现注册了一半的情况。图标 SVG 这类大数据放在 store 的资源里,按需获取。 - 运行时 UI(状态栏文字、通知、任务、事件订阅)来自正在运行的代码,以
PluginEvent的形式经plugin_subscribechannel 到达,由runtimeUi.tsx和bridge.ts处理。 - 由插件代码渲染内容的视图和自定义编辑器,通过
plugin_view_render/plugin_view_event命令提供。 - Web 视图是插件包里自带的页面。客户端在沙箱 frame(不透明源、无网络、无 IDE API)中通过
openidea-pluginURL scheme(src-tauri/src/plugin_web.rs)加载它们,并附带独立的 CSP;页面只能通过 frame bridge 与 IDE 通信。
defineUiPoint;内核(projection、store、bridge)无需改动。
远程项目
窗口打开远程项目时,桌面端把启用的代码插件及其授权发给openidea-server,由它在文件旁边运行:读文件、启动进程、网络请求和诊断都作用于远程机器,process: 程序在那里解析并固定。桌面端的变更(安装、停用、授权)一秒内同步过去。密钥留在桌面端的钥匙串里,服务端通过 plugin_secret 请求索取。项目插件目前还不能在远程项目中运行。
测试宿主
crates/ide-host/tests/plugins.rs 以及其他 plugin_*.rs)会加载官方插件和 crates/ide-plugin/tests/fixtures/ 中夹具的已提交组件。修改插件、SDK 或 wit/ 之后要重新构建它们。pnpm perf:rust 包含插件运行时的基准(plugins/load_50、plugins/cold_activation、plugins/syntax_highlights_2k_lines)。