Skip to content

Standard Library ​

core:stdlib is a toolkit of small helpers for plugin authors: filesystem, subprocess, selection, and config-validation commands that any plugin might need, so writing one doesn't mean re-deriving them. Every command here is reached through call!, never as a plain Scheme function.

Setup ​

scheme
(load-plugin! "core:stdlib")

See Core Plugins for why this call should stay a plain load-plugin!, and Depending on another plugin for checking it's available before your own plugin relies on it.

Selections ​

CallEffect
(call! "stdlib/single-selection?" sels)#t if sels holds exactly one selection
(call! "stdlib/all-single-char?" sels)#t if every selection in sels spans exactly one grapheme
(call! "stdlib/cursor-char-index" sels)0-indexed head char offset of the primary selection in sels, or #f
(call! "stdlib/primary-selection" sels)The primary selection in sels, or #f
(call! "stdlib/selection-anchor" sel)Anchor char offset of the selection sel, or #f
(call! "stdlib/selection-head" sel)Head char offset of the selection sel, or #f
(call! "stdlib/selection-start" sel)First char offset the selection sel covers, or #f
(call! "stdlib/selection-end" sel)Char offset one past the last one the selection sel covers, or #f
(call! "stdlib/selection-primary?" sel)#t if the selection sel is the primary selection, or #f

sels is whatever (buffer-selections pane) returns: a list of selections. A selection is opaque; read it only through these accessors. Offsets are char offsets rather than grapheme ordinals. Start and end bound what the selection covers, end exclusive, whatever the length of its characters. All nine pass a #f sels/sel straight through as #f, so a caller that got one from somewhere else with its own "nothing here" case doesn't need its own guard at every step. (offset->line pane idx) converts an offset to a line number when you need one.

Filesystem ​

CallEffect
(call! "stdlib/find" pred? lst)First element of lst satisfying pred?, or #f
(call! "stdlib/write-file!" path content)Write content to path, creating or truncating it
(call! "stdlib/delete-dir!" dir)Recursively delete dir; idempotent
(call! "stdlib/delete-file!" path)Delete path; idempotent
(call! "stdlib/list-subdirs" dir)Sorted basenames of dir's subdirectories
(call! "stdlib/safe-path-segment?" name)#t iff name is safe to use as a single path component

delete-dir! and delete-file! are idempotent, unlike the Steel engine's own delete-directory!/delete-file!: a missing target is not an error. list-subdirs skips stray non-directory entries that sit alongside a directory tree, like .DS_Store. safe-path-segment? rejects an empty name, ./.., and anything containing a path separator, :, ", or a NUL. Use it before joining a user-typed or downloaded name onto a path.

Subprocesses ​

CallEffect
(call! "stdlib/run!" cmd args #:cwd dir)Spawn cmd/args (in dir, or HUME's working directory if omitted); blocks until exit

Returns (hash 'stdout s 'stderr s 'exit code). 'exit is #f, with the failure reason in 'stderr, if the command couldn't even be spawned or its exit couldn't be waited on. stdlib/run! blocks the whole editor until the command finishes, so it fits something quick (a git rev-parse) rather than anything that might take a moment while the user keeps typing. See Filesystem and processes for run-inline-output! and spawn-async!, the other two ways to run a subprocess.

Git ​

CallEffect
(call! "stdlib/git-repo?")#t when the editor's working directory is inside a git work tree
(call! "stdlib/git-toplevel")Absolute repo root of the editor's working directory, or #f when git is missing or the directory is outside a work tree

Both answer for HUME's own working directory (:pwd), not necessarily the current buffer's. git-repo? is #f inside a bare repository, even though git itself exits successfully there.

Command arguments ​

CallEffect
(call! "stdlib/resolve-lang-arg" pane cmd arg)A typed language-name argument, else pane's buffer's language, else #f after an info message naming cmd

Use this for a : command that takes an optional language name: arg is whatever the user typed after the command, or #f if they typed nothing. Falling back to the invoking buffer's language covers the common case of acting on the language of the buffer the command was invoked for; when neither is available, it logs an info message naming cmd and returns #f so your command can bail out cleanly.

Reports ​

CallEffect
(call! "stdlib/report-section" title items hint)One section as text: title (count), then , hint when items is not empty (hint may be #f), then one indented line per item, or none
(call! "stdlib/show-report!" pane name sections)Shows sections, separated by blank lines, on pane in the read-only buffer [name]

Use these for a : command that lists things: a list longer than one line does not fit on the status line. A later call with the same name replaces that buffer's text. The :plum-list-* commands and :lsp-catalog use them.

Word tokenization ​

CallEffect
(call! "stdlib/split-words" pane str)Every word in str, tokenized using pane's buffer's own word-chars setting

Same classification w/b motions and text objects use, so a word here is exactly what one of those would select. This is (split-words str (get-buffer-option pane "word-chars")). Use it whenever str is that buffer's own content (typically one of its lines) and you want that buffer's own notion of a word. Call split-words directly for text that isn't tied to a particular buffer, or when you have a real reason to classify differently from that buffer's setting.

Plugin configuration ​

CallEffect
(call! "stdlib/config-boolean" plugin cfg key default)cfg's value for key, or default if absent; errors (naming plugin) if the resolved value isn't #t/#f
(call! "stdlib/config-string" plugin cfg key default)Same, erroring if the resolved value isn't a string
(call! "stdlib/config-enum" plugin cfg key default allowed)Same, erroring if the resolved value isn't one of the symbols in allowed
(call! "stdlib/config-integer" plugin cfg key default minimum)Same, erroring if the resolved value isn't an integer, or is below minimum (#f for no minimum)
(call! "stdlib/config-list" plugin cfg key default)Same, erroring if the resolved value isn't a list of strings

cfg is whatever (plugin-config) returns. Every error names the calling plugin (plugin) and the offending key, so a bad #:config value fails at load time pointing at exactly what to fix. See Configuring a plugin for the full picture of reading #:config.

Picker buffer-placement ​

CallEffect
(call! "stdlib/buffer-actions" handler)A picker!/live-picker! #:actions list binding Ctrl-o/Ctrl-t/Ctrl-v/Ctrl-s to handler placed in the current pane, a new tab, a vertical split, and a horizontal split respectively
(call! "stdlib/with-tab" handler)Wraps handler: opens a new tab, then calls handler with the picker's payload
(call! "stdlib/with-vsplit" handler)Wraps handler: splits the focused pane side by side, then calls handler with the picker's payload
(call! "stdlib/with-split" handler)Wraps handler: splits the focused pane stacked, then calls handler with the picker's payload

handler is the same one-argument procedure a picker already passes as on-select (see Custom pickers's #:actions). buffer-actions is the one plugin authors reach for; with-tab/with-vsplit/with-split are its building blocks, for composing a custom #:actions list with different keys or a subset of the four.

Released under the MIT License.