pub struct PluginLoader { /* private fields */ }Expand description
The plugin loader subsystem: owns the runtime, the loaded-plugin set, and the
discovery + load orchestration. Stood up at boot by [install], which
captures the editor environment and registers the loader as a
PluginLoaderHandle service so the user surface reaches it generically.
Implementations§
Source§impl PluginLoader
impl PluginLoader
Sourcepub fn new(host: Arc<PluginHost>) -> Self
pub fn new(host: Arc<PluginHost>) -> Self
Construct a loader over host with no editor environment — the
minimal constructor for tests exercising only the lifecycle spine.
[install] uses with_env to wire the real seams.
Sourcepub fn with_services(host: Arc<PluginHost>, services: LoaderServices) -> Self
pub fn with_services(host: Arc<PluginHost>, services: LoaderServices) -> Self
Construct a loader wired with the editor environment (LoaderServices)
— the boot path ([install]) and headless harnesses / tests. The seams a
plugin declares are driven against the wired handles; an absent handle
makes that seam a logged skip.
Sourcepub fn loaded_count(&self) -> usize
pub fn loaded_count(&self) -> usize
The number of currently-loaded plugins. The spine proof + the PL8.H manager view read it.
Sourcepub fn wired_seams(&self) -> WiredSeams
pub fn wired_seams(&self) -> WiredSeams
Which drain-required services the loader captured from the boot context
([install]). A boot-ordering regression (installing the loader before a
service it depends on registers) silently leaves a field false, turning
that seam’s drain into a NotWired skip — so the boot pin asserts every
flag is set after Editor::boot. Test/introspection affordance.
Sourcepub fn is_loaded(&self, name: &str) -> bool
pub fn is_loaded(&self, name: &str) -> bool
Whether a plugin with manifest id name is currently loaded (the
:plugin-unload <name> / :plugin-reload <name> resolution, PL8.C).
Sourcepub async fn discover_and_load(&self, dir: &Path, tier: TrustTier) -> usize
pub async fn discover_and_load(&self, dir: &Path, tier: TrustTier) -> usize
Discover every plugin under dir and load each, logging + skipping any
that fails (never aborting the others). Returns the count loaded. Runs on
the caller (the multi-thread runtime), off the editor actor.
Sourcepub async fn load_discovered(
&self,
plugin: &DiscoveredPlugin,
tier: TrustTier,
) -> Result<PluginId, PluginLoaderError>
pub async fn load_discovered( &self, plugin: &DiscoveredPlugin, tier: TrustTier, ) -> Result<PluginId, PluginLoaderError>
Load one already-discovered plugin: compile, then either drive the
lifecycle spine (empty provides) or drain each declared seam, and
record its provenance. Returns the host-issued PluginId.
Sourcepub fn request_mode_enablement(&self, mode: &str)
pub fn request_mode_enablement(&self, mode: &str)
PM.7/PM.8 follow-up: honour a require’s enable-mode sugar.
Publishes the same ModeEnablementRequested the manifest
default_mode gate publishes — one mechanism, two ways of asking for
it (a plugin declaring its own default, or a user’s init.rs asking
for it at the call site).
The host never learns the mode-id statically: it arrives in the spec
and is forwarded as an opaque string, so the mode stays the plugin’s
own surface (feedback_mode_owns_its_surface).
A missing bus is a silent skip — the same degradation every other event publisher here uses when the editor is not fully wired (tests, headless harnesses).
Sourcepub fn plugin_status(&self) -> Vec<PluginStatus>
pub fn plugin_status(&self) -> Vec<PluginStatus>
PL8.H.1: a read-only snapshot of every loaded plugin — identity, trust
tier, capabilities granted/denied, and health — for the :plugins
manager view (PL8.H.2/.3). Cloned out under the loaded-set lock, so the
view renders a stable frame while loads/unloads proceed.
Sourcepub fn failed_loads(&self) -> Vec<FailedLoad>
pub fn failed_loads(&self) -> Vec<FailedLoad>
WT.4: the plugins that tried to load this session and could not.
Name-sorted like plugin_status, for the same
reason: the view is read down a column, and discovery order is not
something a user can predict or reproduce.
Sourcepub fn mark_quarantined(&self, plugin: u32, func: String, kind: String)
pub fn mark_quarantined(&self, plugin: u32, func: String, kind: String)
PL8.H.1: mark the plugin plugin quarantined (its instance trapped) — the
body of the Event::PluginCrashed subscription (subscribe_health),
exposed directly so a test can drive the health flip without a live bus.
A crash id matching no loaded plugin is ignored (it may have been unloaded
between the trap and the drain) — never a panic.
Sourcepub fn subscribe_health(self: &Arc<Self>)
pub fn subscribe_health(self: &Arc<Self>)
PL8.H.1: subscribe to Event::PluginCrashed so a trapped plugin’s health
flips to Quarantined in the manager view. Filtered by kind (indexed
dispatch); events drain on the shared runtime via a Channel sink, OFF
the keystroke path (the bus calls the sink lock-dropped). Holds a
Weak<Self> so the drain task never keeps the loader alive — the loop
ends when the loader drops. Called once by [install]; a no-op if no
bus/runtime was wired (the minimal test constructor).
Sourcepub fn subscribe_mode_gates(self: &Arc<Self>)
pub fn subscribe_mode_gates(self: &Arc<Self>)
PM.3: react to <id>.enabled changes — the config gate for a plugin’s
default mode. On a :set <id>.enabled=<bool> (an OptionChanged), map the
option back to the loaded plugin’s default_mode and request the mode’s
enablement to match, so the toggle activates / deactivates it live. Mirrors
Self::subscribe_health; a no-op when no bus/runtime was wired.
Sourcepub fn register_ex_commands(self: &Arc<Self>)
pub fn register_ex_commands(self: &Arc<Self>)
Self-register the :plugin-load / :plugin-unload / :plugin-reload
ex-commands into the runtime-mutable command registry (option A — the
loader owns its full command surface; zero host code). Plain command
names resolve directly via id_by_name (no expand_alias host entry),
exactly like plugin-contributed ex-commands. Called once by [install]
after the loader is constructed; a no-op (logged) if no command registry
was wired.
The apply closures capture a Weak<Self>. The command registry holds
them and the loader holds the registry, so a strong capture was a cycle
that kept the loader — and its plugin host’s engine and threads — alive
after the editor that booted it had been dropped.
Sourcepub async fn run_bulk(
&self,
op: BulkOp,
on_leg: &(dyn Fn(usize, usize, &str) + Send + Sync),
) -> BulkReport
pub async fn run_bulk( &self, op: BulkOp, on_leg: &(dyn Fn(usize, usize, &str) + Send + Sync), ) -> BulkReport
Run op over every loaded plugin, reporting each leg as it starts.
Sequential, deliberately. The obvious reading is that N plugins
should run concurrently, and it is wrong three times over: cargo
already saturates the machine, so N of them contend rather than
parallelise (and can exhaust the disk — a full build tree is tens of
gigabytes); every leg finishes by reloading, which mutates the shared
registries by copy-on-write RCU, so overlapping legs race to publish;
and a user watching the view wants to read which plugin is building
now, not six rows all claiming to be.
A leg’s failure never stops the next one — the same rule install_all
follows at boot, for the same reason: one broken plugin should cost you
that plugin, not the rest.
on_leg(done, total, name) fires BEFORE each leg runs, which is what
lets the :plugins view say which plugin it is on rather than only
what it finished. A caller with nothing to show passes a no-op.
Sourcepub async fn rebuild_all(&self) -> BulkReport
pub async fn rebuild_all(&self) -> BulkReport
Rebuild every loaded plugin from the source it already has, then reload
each. See Self::run_bulk.
Sourcepub async fn update_all(&self) -> BulkReport
pub async fn update_all(&self) -> BulkReport
Update every loaded plugin: bring each source up to date, rebuild,
reload. Pinned plugins are skipped. See Self::run_bulk.
Sourcepub async fn reload_all(&self) -> BulkReport
pub async fn reload_all(&self) -> BulkReport
Re-instantiate every loaded plugin from the artifact already on disk —
no build, no network. See Self::run_bulk.
Sourcepub fn removable_plugin_dirs(&self) -> Vec<(String, PathBuf)>
pub fn removable_plugin_dirs(&self) -> Vec<(String, PathBuf)>
Staged plugin directories that nothing this session claims — what
clean would remove.
A directory is removable only when all of these hold, and each clause is here because dropping it deletes something a user wanted:
- Not loaded. The obvious one.
- Not a load FAILURE this session. A plugin that tried and broke is still a plugin the user asked for; cleaning it would turn “my plugin is failing” into “my plugin is gone” and hide the error the view was showing.
- Not
init. That is the user’s own configuration, not a plugin, and it is never in the loaded set under that name. - Carries a
.sourcemarker. Provenance is what makes removal recoverable — with it the directory can be re-resolved and rebuilt, without it the bytes are the only copy. A hand-staged directory has no marker, and is exactly the case where deleting is unrecoverable.
Returns (name, path) pairs in name order. Reading only — the caller
decides whether to act, which is what lets :plugin-clean show the
list and :plugin-clean! act on it.
Sourcepub fn clean(&self, names: &[String]) -> BulkReport
pub fn clean(&self, names: &[String]) -> BulkReport
Remove the named staged plugin directories.
Takes names rather than re-deriving the list, so the thing the user
confirmed is the thing that gets deleted — Effect::Confirm carries
the payload for this reason (effect.rs, IX.1). Re-deriving after the
prompt would let a reload land in between and change the answer.
Each name is re-checked against Self::removable_plugin_dirs before
its directory goes: a confirmation the user left sitting while a plugin
loaded must not delete the plugin that just arrived.
Sourcepub async fn load_path(
&self,
dir: &Path,
tier: TrustTier,
) -> Result<PluginId, PluginLoaderError>
pub async fn load_path( &self, dir: &Path, tier: TrustTier, ) -> Result<PluginId, PluginLoaderError>
Load a single plugin from an explicit directory — the :plugin-load <path>
entry point (PL8.C). Unlike discover_and_load
(a tree scan that silently skips non-plugin dirs), a direct request
surfaces a bad path as a PluginLoaderError::Discovery the user sees.
Sourcepub fn builds_in_flight(&self) -> usize
pub fn builds_in_flight(&self) -> usize
PM.8b: how many builds are running right now.
The :plugins headerline reads this, per the
async-buffer-status-in-headerline rule — a build takes seconds to
minutes and the user needs to see it is happening somewhere other than
a status line that the next echo will overwrite.
Sourcepub async fn rebuild(&self, name: &str) -> Result<(), String>
pub async fn rebuild(&self, name: &str) -> Result<(), String>
PM.8b: force a fresh build of name from its recorded source, then
reload it.
“Force” is the difference from an ordinary load: the build service short-circuits on a matching stamp, which is exactly what you do NOT want when a user pressed rebuild. The stamp is removed first so the build is unconditional — the user asked, not the staleness check.
Returns the error when the rebuild could not happen or did not
succeed, having left the plugin as it was. A failed rebuild never
unloads a working plugin: PM.5’s StaleKept keeps the old artifact,
and this reloads from it.
Blocking work runs on spawn_blocking; only the reload is awaited.
Sourcepub async fn update(&self, name: &str) -> Result<(), String>
pub async fn update(&self, name: &str) -> Result<(), String>
Bring name up to date with its upstream, then rebuild and reload it.
The difference from Self::rebuild is one argument — the
RefreshPolicy the resolver runs under — but
it is the whole verb: rebuild compiles the source you already have,
update goes and gets a newer one first.
What “newer” means is the source’s to answer, and three of the four kinds answer it without any work here:
| source | update |
|---|---|
Git { rev: None } | fetch, move to the tracked head, rebuild |
Git { rev: Some(_) } | declines — a pin is the answer already |
Local(_) | rebuild; the directory IS the source, so it is always current |
Prebuilt { url } | re-download (the resolver fetches unconditionally) |
The pinned arm declines rather than silently rebuilding, because those are different outcomes and a user who pinned a plugin and then pressed update is owed the reason nothing moved.
Sourcepub fn take_required(&self) -> Vec<RequiredSpec>
pub fn take_required(&self) -> Vec<RequiredSpec>
PM.7b: take the plugins declared via require so far, leaving the
queue empty.
Drained exactly once per boot by the install task. Draining rather than reading is what stops a second call from resolving, building and loading the same set twice.
Sourcepub fn unload(&self, target: &str) -> Option<TeardownReport>
pub fn unload(&self, target: &str) -> Option<TeardownReport>
Unload the plugin named target (its manifest id, or its numeric plugin
id): abort its actor tasks and reverse every registry contribution via
PluginTeardown. Returns the TeardownReport (what each surface
removed), or None if no loaded plugin matched. Synchronous —
teardown and JoinHandle::abort don’t await — so an ex-command apply
closure can call it directly. Idempotent per the teardown contract.
Sourcepub async fn reload(
&self,
target: &str,
tier: TrustTier,
) -> Result<PluginId, PluginLoaderError>
pub async fn reload( &self, target: &str, tier: TrustTier, ) -> Result<PluginId, PluginLoaderError>
Reload the plugin named target: unload it, then
re-load_path from its recorded source directory —
minting a fresh Store with a fresh, untripped Quarantine (the reload
contract, teardown.rs §“Why no reload method”). Errors if target names
no loaded plugin (NotLoaded) or it has
no on-disk source (NotReloadable).
Sourcepub async fn sync_init(
&self,
init_dir: &Path,
tier: TrustTier,
) -> Result<PluginId, PluginLoaderError>
pub async fn sync_init( &self, init_dir: &Path, tier: TrustTier, ) -> Result<PluginId, PluginLoaderError>
Load the init config if it isn’t loaded, or reload it if it is — the
idempotent “make init reflect what’s on disk” the auto-reload watcher
(PL8.D.4) fires on every change to <config>/lattice/init/. First good
build loads; a rebuild reloads (unbinding the old keymaps / commands and
re-applying); a broken rebuild leaves init unloaded (reload unloads
before it fails to re-load), which the next good build heals — is_loaded
is then false, so this loads rather than reloads. Errors propagate for the
caller to log; never panics.
Sourcepub async fn reload_config(
&self,
) -> Result<ReloadConfigReport, PluginLoaderError>
pub async fn reload_config( &self, ) -> Result<ReloadConfigReport, PluginLoaderError>
Rebuild the user’s init.rs in place, then (re)load it — the
source → artifact step :reload-config and the plugins view’s
rebuild-of-init need and that the bare artifact reload
(reload / load_path) cannot do.
This mirrors the boot path (install::build_init +
sync_init). Before it existed, :reload-config
re-instantiated the stale on-disk init.wasm, so an edited option
(e.g. tabstop) took effect only after a full restart — the one path
that recompiled. init’s SourceRecord is Unknown (it is discovered
directly, never “installed”, so it has no .source marker) and it
compiles in place rather than staging into the plugin cache, so it
cannot go through the generic rebuild_with
pipeline — hence this dedicated seam.
Loads at the Bundled tier the boot path uses —
the user’s own config is trusted, not a third-party plugin.
A build failure with a previous artifact present keeps the last good
config running and reports ConfigBuildStatus::BuildFailed with the
compiler error (an Ok whose applied_new_config is false); a build
failure with no previous artifact surfaces as Err — there is
nothing to load.
Auto Trait Implementations§
impl !Freeze for PluginLoader
impl !RefUnwindSafe for PluginLoader
impl !UnwindSafe for PluginLoader
impl Send for PluginLoader
impl Sync for PluginLoader
impl Unpin for PluginLoader
impl UnsafeUnpin for PluginLoader
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more