You're reading docs for unreleased changes —  switch to v0.19.0.
Skip to content

Plugins ​

Plugins are written in Scheme, the same language as your config, so the line between configuring HUME and extending it is thin.

This page covers installing and loading plugins other people wrote; Writing Plugins covers making your own.

Plugins are installed and updated by PLUM, a core plugin. See Core Plugins to enable it.

Installing a plugin ​

Add a load-plugin! call to your init.scm.

scheme
(load-plugin! "core:stdlib")
(load-plugin! "cvlmtg/grep.hume")

INFO

cvlmtg/grep.hume adds one g / keybind to open a picker with a live rg (or grep) search behind it, re-running the search as you type.

Run :plum-install-plugins to clone the plugin from GitHub, then :reload-config to load it. PLUM never installs anything on its own.

core:stdlib is grep.hume's own dependency, not something every plugin needs; check each plugin's docs for what it requires.

See How plugins are loaded for when a plugin's code runs.

If a plugin supports configuration, pass it with #:config:

scheme
(load-plugin! "core:stdlib")
(load-plugin! "core:vim-keybind" #:config (hash 'change-to-eol 'off))

See Configuring a plugin for what a plugin does with this value, and the plugin's own docs for which keys it understands.

Pinning a version ​

PLUM installs a plugin's latest commit. To stay on a release, follow a branch other than the default one, or stay on one commit, give its tag, branch name or commit id with #:ref:

scheme
(load-plugin! "cvlmtg/grep.hume" #:ref "v1.0.0")

:plum-install-plugins checks a new plugin out at that ref, and :plum-update-plugins moves an installed one onto it. A plugin pinned to a tag or a commit stays on it; a plugin pinned to a branch gets that branch's latest commit.

After changing or removing a #:ref:

  1. Run :reload-config.
  2. Run :plum-update-plugins.
  3. Run :reload-config again to load the new version.

A commit id can be the short form git log --oneline shows, as long as the commit is on one of the plugin's branches or tags. A full commit id also works for a commit on none of them, if the plugin's host still has it. If PLUM can't find the ref, installing or updating that plugin fails with a message naming it, and an installed plugin stays as it was.

Plugin status ​

:plugin-status

Shows one row per plugin entry: whether it is declared (waiting), loaded or failed, for a declared entry the commands, events and languages still waiting to trigger it, and for a loaded entry the languages it works in. A plugin that loads in pieces has a row for each file, and a local file shows under its ./ path. A plugin named in init.scm but not installed shows as absent, so you can tell what :plum-install-plugins still has to fetch.

Recovering from a failed plugin ​

A plugin that fails to load doesn't stop the rest of init.scm: every plugin after it still loads, and every plugin before it is unaffected. A lazy plugin loads when its first trigger fires, so its failure shows up then, not at startup. :messages has the error, naming the plugin and the file and line the problem is in. :plugin-status shows the plugin as failed, including one whose manifest.scm failed before it declared anything.

A failing plugin from a git repository (rather than one you're editing yourself) may already have a fix upstream. Run :plum-update-plugins to fetch the latest version of every installed plugin (or of its pinned branch), then :reload-config to try again.

How plugins are loaded ​

(load-plugin! "name") is the call to put in init.scm. When the plugin loads depends on what it ships:

Plugin shipsTiming
a manifest.scmLazy: code deferred until first use
only a plugin.scmEager: code runs during startup

Lazy plugins ship a manifest listing what the plugin offers (commands, typed commands, events, languages). load-plugin! registers those entries and doesn't run the plugin's code until the first one is used.

Eager plugins have no manifest, so plugin.scm runs during startup. A plugin that only adds keys, options or hooks works this way.

A directory with neither manifest.scm nor plugin.scm is an error. A missing core: plugin logs an error; a missing user/repo plugin logs an info message, and :plum-install-plugins installs it.

Choosing your own triggers ​

declare-plugin! lists the entries that wake a lazy plugin. Declare an installed plugin before its load-plugin! line to use your triggers instead of its manifest's:

  • #:commands: editor-command names the plugin provides. The names work in key bindings and call! right away; the first use loads the plugin, then runs the command. So you can bind a lazy plugin's commands before it loads, even though the plugin's own bindings aren't in place yet:

    scheme
    (declare-plugin! "cvlmtg/grep.hume" #:commands '("grep/picker"))
    (load-plugin! "cvlmtg/grep.hume")
    (bind-key! 'normal "space g" "grep/picker")
    ; pressing <space>g the first time loads cvlmtg/grep.hume, then runs grep/picker
  • #:typed-commands: typed-command names the plugin provides (defined with define-typed-command!). They work like #:commands, but the names appear in : Tab completion and run only from the : prompt, never from a key or call!:

    scheme
    (declare-plugin! "cvlmtg/grep.hume" #:typed-commands '("grep"))
    (load-plugin! "cvlmtg/grep.hume")
    ; typing :grep the first time loads cvlmtg/grep.hume, then runs grep
  • #:events: lifecycle hooks that trigger loading, as a list of symbols (e.g., '(on-buffer-open)). Next to #:languages it takes only hooks that are about no buffer, such as on-focus-gained, unless the languages include "*": a hook about a buffer raises an error, since setting the buffer's language loads the plugin first.

  • #:languages: buffer language names that trigger loading when a buffer's language is set to one of them. Once loaded, the plugin works only in buffers of those languages: in any other buffer its hooks and completions stay silent, as if the buffer were not open. Its commands still run anywhere. '("*") loads the plugin for any language and limits nothing. Detection only finds languages HUME already knows, so for a language a plugin defines itself, put the define-language! in your init.scm (see Teach HUME a new language) and let the plugin load lazily on that name.

A plugin that already has a declared entry keeps those triggers when load-plugin! runs: the manifest is not read and the plugin stays lazy. Put load-plugin! right after the declare-plugin!, because a plugin that activates during startup before its load-plugin! line sees an empty config. A declare-plugin! placed after the plugin's load-plugin! raises an error.

For example, to use core:lsp only in Rust buffers, and load it when you use one of its commands:

scheme
(declare-plugin! "core:lsp"
  #:languages '("rust")   ; load for, and work in, only the languages you name here
  #:commands '("lsp/hover" "lsp/goto-definition" "lsp/goto-declaration"
               "lsp/goto-type-definition" "lsp/goto-implementation" "lsp/references"
               "lsp/goto-next-diagnostic" "lsp/goto-prev-diagnostic"
               "lsp/rename" "lsp/format-source" "lsp/format-document" "lsp/code-actions"
               "lsp/document-symbols" "lsp/workspace-symbols")
  #:typed-commands '("diagnostics" "format-source"
                      "lsp-status" "lsp-stop" "lsp-restart"
                      "toggle-inlay-hints"))
(load-plugin! "core:lsp")

Opening a file of another language still starts the servers its server list names, but that buffer shows no diagnostic marks, completions or inlay hints from them. core:lsp's commands, such as lsp/hover, still work there.

WARNING

#:events '(on-lsp-attach) by itself never activates the plugin: no server attaches before the plugin loads, so the event never fires. List the languages you want servers for in #:languages, or list the commands in #:commands/#:typed-commands (as above).

Completions need core:lsp to be active already: Ctrl-Space and a server's trigger characters run the completion-trigger editor command, not one of core:lsp's commands, so #:commands/#:typed-commands alone don't get completions working. List the languages you use in #:languages to get completions along with everything else.

Lazy local files ​

A script beside your init.scm can load lazily too. Name it with a path that starts with ./, relative to the directory of init.scm:

scheme
(declare-plugin! "./my-lazy.scm" #:commands '("my-cmd"))

The name must end in .scm and must not contain .., and the file must exist, or init.scm fails with an error. #:entry is not accepted. See Plugin lifecycle for how the plugin functions treat a local file.

Core plugins ​

HUME ships several core plugins; see Core Plugins for the full list and what each does.

Released under the MIT License.