Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

btv.plugins

btv.plugins.disabled()

btv.plugins.disabled() -> whether the plugin manager is off in this editor: an app embedding it turned it off, so plugins are neither declared, loaded, installed nor updated through it. Every other btv.plugins call then raises E5805; plugins under the config’s pack/*/start/ still load.

Defined in plugins.lua.

btv.plugins.on_change(fn)

M.on_change(fn) — subscribe to manager state changes (task transitions, plugin loads). Returns an unsubscribe function. The manager UI re-renders on each call.

Defined in plugins.lua.

btv.plugins.load(name)

Load a plugin by name: dependencies first, then put it on the runtimepath, source its plugin/ scripts, and run its config. Idempotent (a second call is a no-op) and cycle-safe (a _loading guard breaks dependency loops). Returns a promise resolving true when newly loaded, false if already loaded / in flight; REJECTS (loud) if the plugin is not installed.

Defined in plugins.lua.

btv.plugins.on_loaded(name, fn)

btv.plugins.on_loaded(name, fn) — run fn once name is fully loaded (its plugin/ scripts sourced and its config finished, an async config awaited). fn is called with the plugin name. Returns an unsubscribe function.

This is the hook to reach for when your config must run after someone else’s plugin — it is race-free in the direction the raw PluginLoaded event is not. Registering btv.on("PluginLoaded", { pattern = name }, …) only ever hears a load that happens later, so the same code silently does nothing when the plugin turns out to be eager and already loaded (a plain ordering flip in your config, or a spec someone else made non-lazy). on_loaded closes that: if the plugin is already loaded it calls fn immediately, otherwise it waits for the plugin’s PluginLoaded. Either way fn runs exactly once.

The plugin need not be declared yet — on_loaded before the btv.plugins{…} that declares it is fine, and is the usual shape in an init.lua.

btv.plugins.on_loaded("bemtvi-lspconfig", function()
  require("bemtvi-lspconfig").setup()
end)

For “every eager plugin is ready” (not one named plugin) use the PluginsLoaded event instead: btv.on("PluginsLoaded", {}, fn).

Defined in plugins.lua.

btv.plugins.add(specs)

Register one or more plugin specs. Accepts a single spec (string or table) or a list of them. Each is normalized and stored; a lazy spec’s triggers are armed (and its init run) immediately, while an enabled eager spec that is already on disk begins loading. Returns the name of the LAST spec added (so a single-spec add — as dependency registration uses — yields that dependency’s name).

The FIRST declaration of a name arms it; re-declaring the same name (a config re-sourced, or a shared dependency re-added through a second plugin) updates the stored spec without re-arming — re-arming would double-register every trigger (autocmd / command / keymap) and re-run init.

Defined in plugins.lua.

btv.plugins.setup_manager(opts)

btv.plugins.setup_manager{ root=, config=, system=, lockfile=, github= }: override the install root (a test points it at a temp dir), the config dir, the system-plugin dir, the lockfile path (default <config>/bemtvi-lock.json), or the shorthand URL template. Merges onto the defaults. (Named setup_manager, not setup, so it reads distinctly from a plugin’s own require("plugin").setup{} and never looks like the call that declares plugins — that is btv.plugins{...} / btv.plugins.add.)

Defined in plugins.lua.

btv.plugins.locked()

btv.plugins.locked() — a synchronous snapshot of the last read/written lockfile. Cheap (no disk); call _read_lock() first if the file may have changed underneath.

Defined in plugins.lua.

btv.plugins.lock(opts)

btv.plugins.lock() — record every managed, installed plugin’s current commit to the lockfile. Resolves the written table. Writes only when the resolved set differs from what is already on disk, so a no-op sync leaves the file (and the user’s git status) untouched.

opts.plugins (a name, or a list of names — plus their dependencies) records just those plugins, carrying every other entry over verbatim: :PluginLock alpha pins the one plugin you have finished testing without also freezing everything else at whatever it happens to sit on today.

opts.realized (internal) narrows the re-record the same way for the plugins a verb just put in place — the difference being that a realized plugin’s spec tag is ATTESTED as the pin that produced its commit, which a hand-typed :PluginLock cannot claim. See resolve_lock.

Defined in plugins.lua.

btv.plugins.restore(opts)

btv.plugins.restore() — move every managed plugin to the commit the lockfile records: the “undo the update that broke my editor” verb, and the reason recording commits is worth anything. Resolves

{ restored = { name, … }, current = { name, … }, failed = { { name, message }, … } }

so a caller can tell the three outcomes apart. A plugin already at its locked commit is current (never re-checked-out); a dev dir plugin, an uninstalled one, and one with no lock entry are skipped silently (there is nothing recorded to restore them to).

A commit that cannot be reached even after unshallowing lands in failed and the checkout is left exactly as it was — a partial rollback that CLAIMED success would be worse than none, since the user would stop looking for the real problem. The promise still resolves (so the other plugins’ outcomes survive) and :PluginRestore reports the failures loud.

opts.plugins (a name, or a list of names — plus their dependencies) rolls back only those plugins: the update that broke your editor is usually one plugin, and there is no reason to un-do the twelve that are working.

Defined in plugins.lua.

btv.plugins.install(opts)

Install every declared, enabled plugin that is missing. Returns a promise of the count installed; activates any newly-present eager plugins as it finishes.

A failure aborts the run LOUD (a half-installed tree must never look complete), but the lockfile is recorded either way: the plugins that did land are on disk, and a lockfile that no longer describes the tree is precisely the silent disagreement it exists to prevent. The rejection is re-raised after the record is written.

Only the FRESH clones are recorded (they land exactly on their pin / lock entry, so their HEAD is evidence). A plugin that was already present is left as the lockfile has it: install does not check where it sits, so overwriting its entry from the checkout would throw away a lockfile the user just pulled in — the very thing the next sync pass is about to realize.

opts.plugins (a name, or a list of names) installs only those — plus their dependencies, since a plugin whose dependency is missing does not load. Every other declared plugin is left exactly as it is, lockfile entry included.

Defined in plugins.lua.

btv.plugins.update(opts)

Fast-forward every installed, unpinned plugin, ADVANCING past the lockfile (and re-recording it) — the cargo update half. opts.advance = false instead reproduces the lock, moving a locked plugin onto (or leaving it at) the recorded commit; that is what sync passes. Returns a promise of the count of plugins whose checkout MOVED. Records the lock even when a plugin fails, for the reason install does.

opts.plugins (a name, or a list of names — plus their dependencies) updates only those: the usual reason to update at all is one plugin whose fix you are waiting for, and pulling in eleven other people’s changes at the same time is how a working editor becomes a bisect. Every other plugin keeps its checkout AND its lockfile entry.

Defined in plugins.lua.

btv.plugins.sync(opts)

Install the missing, then update the rest — the one-shot “make the world match my declarations”. Returns a promise of the count NEWLY INSTALLED (so a UI can tell whether a fresh clone landed and prompt to restart). A lockfile makes this the “realize my declared state” verb, NOT an update: it installs what is missing at the locked commits, MOVES an existing checkout onto them when it has drifted (pull a colleague’s newer lockfile and sync, and you get the tree it names — reproducing the lock only for clones that happen to be missing would honour the file by accident of what is on disk), realizes any commit/tag pin the spec has changed, and fast-forwards only what nothing pins. Advancing PAST the lock is :PluginUpdate’s job — if sync advanced, the lockfile would be honoured for the instant between the install and the update pass and then thrown away, which is no lockfile at all. (Locking is inherited from the two halves — install records the fresh clones and update records the rest, after convergence. No third write here.)

opts.plugins (a name, or a list of names — plus their dependencies) realizes only those, by handing the same scope to both halves.

Defined in plugins.lua.

btv.plugins.clean(opts)

Remove cloned plugin directories under the install root that no plugin declares (a dev dir plugin lives outside the root and is never touched). Returns a promise of the removed names.

opts.plugins (a name, or a list of names) instead removes EXACTLY those clones, whether or not a spec still declares them — the “this checkout is wrong, give me a fresh one” verb (remove, then install). Unlike every other scoped verb it does not pull in dependencies: deleting a checkout you did not name is destruction by inference. A local dir plugin fails LOUD instead of being deleted — that is your own working tree, not a managed clone. A plugin still declared keeps its btv.shada.plugin data (it is coming back); only a name nothing declares any more has its namespace forgotten.

Defined in plugins.lua.

btv.plugins.list_system()

A synchronous snapshot of the system tier: { { name=, dir= }, … }, sorted by name.

Defined in plugins.lua.

btv.plugins.system(spec)

btv.plugins.system(spec) — inject a plugin into the system tier: clone/copy it into the system dir (via the local-always seam) and load it into the current session, so it takes effect now AND is re-seeded by the client into every future session (the VS Code “install a connector” move). spec is a normal plugin spec (string shorthand / table). Returns a promise of the plugin’s name. Callable form for init.lua.

Defined in plugins.lua.

btv.plugins.promote(name)

btv.plugins.promote(name) — promote an already-declared managed plugin into the system tier: clone its on-disk checkout into the system dir and register it, so it persists into every future session. Loads the system copy now unless the plugin is already loaded this session. REJECTS (loud) for an unknown plugin. Returns a promise of the name.

Defined in plugins.lua.

btv.plugins.recommend(specs)

Register the recommended set (replacing any prior). A recommended spec may carry every normal field EXCEPT a config/init function — those must be a STRING of Lua source, because the set is serialized into the user’s plugins.lua and a function cannot be written out. Fails loud on a function hook rather than silently dropping it.

Defined in plugins.lua.

btv.plugins.bootstrap()

First-run flow: on a fresh setup (the user has declared no plugins of their own, a recommended set exists, and we have not asked before), open the WELCOME view — a floating checklist (prelude/plugins_ui.lua → M.ui.welcome) explaining that bemtvi ships minimal and offering the recommended set pre-ticked, each item untickable. The chosen subset is written to the user’s config and installed; an empty / skipped choice does nothing. Returns a promise. Wired to VimEnter, and callable directly. Re-entrant-safe (_prompting guard) and asks only once (the marker, written before showing the view so a cancel still never nags again).

Defined in plugins.lua.

btv.plugins.list()

A synchronous snapshot of the declared set (declaration order): each { name, url, dir, lazy, pinned, loaded, locked }. Cheap (no disk) — loaded reflects the in-memory load state and locked the commit the LAST-READ lockfile records (nil when unlocked). Use status() for the disk-checked installed / head / drifted.

Defined in plugins.lua.

btv.plugins.status()

Like list() but disk-checked — a promise, since the reads are off-tick. Adds installed, the checked-out head commit, and drifted (the checkout is at a different commit than the lockfile records). Drift is the signal that the tree no longer matches what the config’s lockfile promises, so :PluginList and the dashboard can say so instead of leaving the user to compare shas by hand.

Reads the lockfile from disk first, so drift is computed against the CURRENT file (the user may have just checked out a different one) rather than whatever was last read.

Defined in plugins.lua.