Skip to main content

lattice_plugin_manager/
lib.rs

1//! The buffer-backed `:plugins` manager view: `plugins-mode` and the
2//! `:plugins` ex-command (PL8.H.2).
3//!
4//! A pure PROVIDER crate (the `oil` / `lattice-dashboard` shape): it owns
5//! `plugins-mode` (major, read-only) and the `:plugins` ex-command, and reads the
6//! loader's `plugin_status()` (PL8.H.1) via the `PluginLoaderHandle` service at
7//! activation. Everything wires through the generic [`SubsystemBoot`] seam, so
8//! adding it to the editor is **one line** in the host's Phase-B install list and
9//! **zero** host internals — no `Editor::` method, no host `Action` variant (the
10//! mode-ownership acid test).
11//!
12//! Open flow, all generic primitives (no provider-specific host code):
13//!   1. `:plugins` (registered here) returns
14//!      [`Effect::OpenSyntheticBuffer`](lattice_grammar::Effect::OpenSyntheticBuffer)
15//!      `{ name: "*plugins*", mode_id: "plugins-mode" }`.
16//!   2. The host generically ensures that buffer under `plugins-mode` + activates
17//!      it (`Editor::open_synthetic_buffer`).
18//!   3. `PluginManagerMode::on_activate` projects the status table into the
19//!      buffer and subscribes to `PluginCrashed` for live health.
20//!
21//! Interactivity (reload / unload / describe chords) is PL8.H.3.
22
23use std::sync::Arc;
24
25use lattice_grammar::{Args, Effect, ExCommandSpec, LatencyClass, SurfaceForm};
26use lattice_mode::SubsystemBoot;
27
28mod actions;
29mod headerline;
30mod mode;
31mod render;
32
33pub use mode::PluginManagerMode;
34pub use render::{
35    PLUGINS_BUFFER_NAME, PLUGINS_MODE_ID, render_status, render_status_with_failures,
36};
37
38/// Install the plugin-manager view: register `plugins-mode` + the `:plugins`
39/// ex-command. Seated in the host's Phase-B install list (before the registry
40/// freeze), so it registers directly via `boot.modes_mut()` / `boot.commands_mut()`.
41///
42/// No dependency on the loader being installed first: the mode resolves
43/// `PluginLoaderHandle` at *activation* (by which point boot is complete), and
44/// the ex-command returns a static effect. A missing loader service at activation
45/// degrades to an empty buffer, never a panic.
46pub fn install(boot: &mut impl SubsystemBoot) {
47    boot.modes_mut()
48        .register(PluginManagerMode)
49        .expect("plugins-mode registers without conflict");
50
51    // PL8.H.3: the in-view `action:plugins-*` commands (dead-body) so the mode's
52    // keymap `cmd:` names resolve; the mode's `action_handlers` do the work.
53    actions::register_actions(boot.commands_mut());
54
55    boot.commands_mut().register_ex_command(
56        "plugins",
57        "Open the plugin manager (`:plugins`) — a buffer listing every loaded \
58         plugin with its health (quarantined after a crash), trust tier, and \
59         capabilities granted/denied. Read-only; reload / unload act on plugins \
60         via `:plugin-reload` / `:plugin-unload` (in-view chords land in a \
61         follow-up).",
62        plugins_spec(),
63    );
64}
65
66/// The `:plugins` ex-command: takes no argument, returns the generic
67/// open-synthetic-buffer effect. `Reflex` — it does no blocking work on the
68/// dispatch path (the host runs the open; the mode projects content off-thread).
69fn plugins_spec() -> ExCommandSpec {
70    ExCommandSpec {
71        latency_class: LatencyClass::Reflex,
72        accepts_bang: false,
73        accepts_range: false,
74        parse_args: Arc::new(|_line: &str, _bang: bool| Ok(Args::None)),
75        apply: Arc::new(|_ctx| {
76            Ok(Effect::OpenSyntheticBuffer {
77                name: PLUGINS_BUFFER_NAME.to_string(),
78                mode_id: PLUGINS_MODE_ID.to_string(),
79                content: None,
80                cursor: None,
81                activate_minor: None,
82            })
83        }),
84        args_schema: Vec::new(),
85        surface_form: SurfaceForm::Keyword,
86    }
87}