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

Configuration

Options, key bindings, the statusline, plugins, and language servers are all configured in one language, in one file. There's no separate config format to learn — init.scm is a Scheme program, so anything you can compute you can configure.

HUME can be configured two ways: the :set command for runtime changes during a session, or an init.scm file for persistent configuration loaded at startup.

HUME reads persistent configuration from:

  • macOS / Linux: $XDG_CONFIG_HOME/hume/init.scm (defaults to ~/.config/hume/init.scm)
  • Windows: %APPDATA%\hume\init.scm

Pass --config <FILE> (see Command-line Flags) to load a different file instead — themes and the data directory still resolve from the standard directories above. :reload-config re-runs whichever file the session started from.

If the file does not exist, HUME starts with defaults — except an explicit --config path, which is a startup (and reload) error if missing: unlike the default init.scm, an explicitly named file is expected to be there, so :reload-config reports an error rather than silently resetting to defaults if it's gone by the time you reload. If it fails partway through, the error is reported in :messages and everything up to that point stays applied — so a broken line late in the file leaves you half-configured rather than back at defaults. Fix it and run :reload-config to re-run the file without restarting.

:reload-config starts from a clean slate: every option, key binding, hook, command, and plugin goes back to its default first, then the file runs again — so removing a line from init.scm and reloading does undo what it did. Any :set global/:set buffer/:theme change you made during the session is discarded too, not just what init.scm set, with two exceptions: a pane-scoped :set pane override, which stays as you left it (panes are editing state, not config), and an explicit :set buffer language=<name>, which is restored after the reload rather than discarded — detection can't reconstruct it on its own (that's exactly why you had to set it explicitly), so losing it on every reload would be more surprising than keeping it. If the file fails partway through this time, you're left with defaults plus whatever ran before the error, same as at startup.

Buffers stay open and language servers stay attached across a reload — it behaves as if every open file were closed and reopened. Completion triggers, inline diagnostics, and any per-language setup your config applies (e.g. from on-language-set) come back too, without restarting the language server or losing your place in the file.

A reference config ships as init.scm.example inside the runtime directory — share/hume/init.scm.example in the macOS/Linux release archive, runtime/init.scm.example on Windows or in a source checkout (see File locations for the general rule); copy it to the path above if you want a starting point, or see Example init.scm below.

Setting options

There are two ways to set an option: the :set command for runtime changes, or set-option! in init.scm for persistent defaults.

:set command

The :set command takes a scope and a key=value pair. The scope is required:

:set global <option>=<value>     set the global default
:set buffer <option>=<value>     override for the current buffer only (takes precedence over global)
:set pane <option>=<value>       override for the current pane only (view-scoped settings)

For a buffer option (the Buffer options table below), :set global takes effect immediately in every buffer that has no override of its own — not just newly opened ones. wrap-mode additionally accepts :set pane, which pins one pane's wrap style above both the buffer and global setting (see Text wrap).

Changes apply to the current session and are not persisted — for persistent configuration, use init.scm (below).

From init.scm

scheme
(set-option! "option-name" value)

Sets the global default. The value is a string, boolean, or integer. Callable from init.scm, a plugin body, or a command/hook body — anywhere Scheme code runs.

scheme
(set-option! "line-number-style" "absolute")
(set-option! "tab-width" 2)

init.scm is a real Scheme program, not a flat list of settings, so you can react to what's being opened rather than only set fixed defaults. The most common case is configuring an option per file type: register an on-language-set handler and call (set-buffer-option! bid "option" value) to override just that buffer (see Hooks for the full hook API):

scheme
; 2-space indentation for Markdown buffers
(register-hook! 'on-language-set
  (lambda (bid lang)
    (when (equal? lang "markdown")
      (set-buffer-option! bid "tab-width" 2))))

; word-wrap Markdown buffers, leave source code unwrapped
(register-hook! 'on-language-set
  (lambda (bid lang)
    (when (equal? lang "markdown")
      (set-buffer-option! bid "wrap-mode" "word"))))

; treat '-' as a word character in CSS-family buffers, so `w`/`b`/`mm`/`*`
; see "foo-bar" as one word instead of three
(register-hook! 'on-language-set
  (lambda (bid lang)
    (when (member lang '("css" "scss" "less"))
      (set-buffer-option! bid "word-chars" "-"))))

Global options

Set with :set global <option>=<value> or (set-option! "option" value). All of these are global-only.

For a bool option, :set accepts true/false, on/off, yes/no, or 1/0; from Scheme, pass #t/#f.

OptionTypeDefaultDescription
themestring"" (built-in sand)Active color theme name
cursor-shape-insertblock/bar/underlinebarCursor shape while in Insert mode, applied to every cursor when multiple are active
scrolloffinteger3Minimum lines kept above/below cursor
object-jump-aligntop/center/offcenterWhere the view lands after jumping forward to a paragraph or structural object (}, g f, …); top is still subject to scrolloff
mouse-enabledbool#tEnable mouse support
mouse-scroll-linesinteger3Lines per mouse scroll tick
mouse-selectbool#fMouse drag creates selections
jump-list-capacityinteger ≥ 1100Max jump list entries
jump-line-thresholdinteger5Line distance to record a jump
history-capacityinteger ≥ 1100Max entries per :///? prompt history
undo-levelsinteger0Max undo states kept per buffer; 0 means unlimited. Once the limit is reached, the oldest states — including whole abandoned branches — are dropped as new edits are made
steel-init-budget-msinteger ≥ 110000Max evaluation time (ms) for init.scm and each plugin activation. Setting it from init.scm has no effect on that same run — the budget is read before each file/plugin evaluation starts, so a change only takes effect for evaluations after it, i.e. the next plugin activation or the next session
steel-command-budget-msinteger ≥ 11000Max Steel command evaluation time (ms)
popup-borderbool#tShow popup borders
syntax-highlight-max-bytesinteger ≥ 11048576Max bytes for syntax highlighting
pane-dividersbool#tDraw a 1-cell divider between sibling panes
statuslineleft | center | rightsee StatuslineThree |-separated sections, each a comma-separated list of element names (empty sections allowed), e.g. Mode,FileName||Position
statusline.mode-colorsbool#tTint the whole statusline with the current mode's color; off shows the theme's base ui.statusline color in every mode

The lsp.* options below configure core:lsp — see Language Servers for setup, commands, and how they're used.

OptionTypeDefaultDescription
lsp.inlay-hintsbool#fShow inferred types and parameter names inline, next to the code they describe
lsp.diagnostics-severity-floorerror | warning | info | hinthintLowest diagnostic severity to display
lsp.request-timeout-msinteger ≥ 110000How long to wait for a language-server reply
lsp.viewport-debounce-msinteger ≥ 1150Delay before re-requesting hints after scrolling
lsp.format-max-rangesinteger ≥ 116Above this many disjoint ranges, :lsp-fmt warns and formats nothing instead of sending one request per range (a server that batches ranges into a single request isn't capped)

Buffer options

These options have a global default that every buffer without its own override resolves to — including buffers already open when you change it, not just ones opened afterward — and a per-buffer override that takes precedence when present. Set the global default with :set global <option>=<value> or (set-option! "option" value); override the current buffer with :set buffer <option>=<value>, or from a script with (set-buffer-option! buffer-id "option" value) — see Plugins for setting per-language overrides from the on-language-set hook.

language is an exception, it has no global default — it is auto-detected per buffer and can only be set with :set buffer language=<name>.

OptionTypeDefaultDescription
wrap-modenone | soft[:N] | word[:N] | indent[:N]indentLine wrapping. N is the wrap column (0 or omitted = pane content width). Also accepts :set pane wrap-mode=<value> to pin one pane above the buffer/global setting — see Text wrap
tab-widthinteger, 1–2554Spaces per indent level
indent-guidesbool#tDraw vertical guides at each indentation level
tab-stylehard | softhardWhat Tab inserts: hard = literal \t; soft = spaces to next tab stop
line-number-styleabsolute | relative | hybridhybridLine number display in the gutter
auto-pairs-enabledbool#tEnable auto-pair insertion
select-inserted-textbool#tLeaving Insert mode keeps the text you typed selected, instead of leaving a plain cursor
word-selects-whitespacebool#tw/W/b/B and mm/MM cover the whitespace before the destination word (trailing instead, for the first word of a line); #f selects the bare word instead
word-charsstring""Extra characters counted as part of a word by w/b, mm, miw/maw, select-word-nearest-on-line, Ctrl+W, and * — e.g. - makes foo-bar one word instead of three. Also affects quote auto-pairing (', ", ` — not bracket pairs), the identifier under the cursor used by plugin commands (e.g. rename), and where a completion without a server-supplied replace range starts. Does not affect W/B/MM, which already treat punctuation as part of a WORD. No global default ships; set it per language from an on-language-set hook (see below). Whitespace and newline characters are rejected
signcolumnalways[:N] | auto[:N]alwaysGutter column for plugin-supplied signs (diagnostics, git changes, etc). Bare always/auto sizes the column to one column per registered sign source — a source claims its column the moment the plugin registers it, so the width doesn't change as individual signs come and go; :N pins it to exactly N columns (1–127) instead, hiding whichever lower-priority sources don't fit. auto additionally collapses to zero width when no signs are visible
autoreadbool#tPrompt to reload when the current buffer's file changes on disk. #f only warns — reload manually with :e!
whitespace-spacenone | all | trailingnoneWhen to render space indicators. Also reveals invisible Unicode spaces (non-breaking and ideographic) with a distinct marker
whitespace-tabnone | all | trailingnoneWhen to render tab indicators
whitespace-newlinenone | allnoneWhen to render newline indicators
languagestring(auto-detected)Language for syntax highlighting

Characters the terminal cannot be shown — control characters, and invisible ones such as a zero-width space or a bidirectional override — are always displayed as their codepoint (<200b>), styled with the theme's ui.virtual.invisible scope, whatever the options above are set to. They are not whitespace you can choose to hide: left invisible they misalign the rest of the line, and an unseen bidirectional override can make code read differently from how it runs.

Text wrap

Text wrap is controlled by three layers — a global default, a per-buffer override, and a per-pane pin — plus a per-pane toggle command.

  • wrap-mode is a buffer option (see Buffer options): set a global default with :set global wrap-mode=<value> or set-option!, or override one buffer with :set buffer wrap-mode=<value> or (set-buffer-option! bid "wrap-mode" value). To wrap by file type — markdown but not source code, say — set it per language from an on-language-set hook (see Plugins).
  • :set pane wrap-mode=<value> pins the style for the pane you're currently in and the buffer it's currently showing, live, above both the buffer and global setting, without affecting other panes on the same buffer. Switching that pane to a different buffer resolves the new buffer's own setting instead; switching back returns the pin. There's no command to clear a pin back to following the buffer/global setting — pin it to a different value, or close the buffer, to move on from it.
  • :wrap (alias of :toggle-soft-wrap) toggles wrapping on or off for the current pane and buffer. Turning it off pins the pane to no wrap; turning it back on restores whatever it was doing before — the buffer/global setting, if the pane wasn't pinned, or the exact style you pinned it to with :set pane wrap-mode=…. If that restores a setting that doesn't actually wrap, it pins the configured global style instead (or indent, if the global itself is none) — :wrap always visibly wraps.

Two panes showing the same buffer can still wrap independently once one of them is pinned with :set pane or :wrap — that's what the per-pane layer is for.

Accepted values:

  • none — no wrapping; long lines scroll horizontally.
  • soft — break at the pane width, splitting at any character (may split a word in the middle).
  • word — break at the pane width but prefer whitespace, so words aren't split.
  • indent — like word, but continuation rows are indented to match the line's leading whitespace, so nested code stays visually nested (this is the default).
  • :N suffix — wrap at column N instead of the pane's content width (e.g. word:80). 0 or omitted means content width.

Themes

scheme
(set-option! "theme" "sand")

To see which themes are available, type :theme and press Tab.

Custom themes are TOML files placed in the themes/ subdirectory of your HUME config directory — hand-authored, alongside init.scm. A theme installed by a tool instead goes in the themes/ subdirectory of your HUME data directory (see File locations); a config-dir theme of the same name wins.

HUME reads the Helix theme format and aims to support Helix themes as they are written. It is not there in every detail yet, but it is close: most Helix themes load and render unchanged. A scope can be written as a flat key ("ui.cursor" = { fg = "..." }) or as a TOML section header ([ui.cursor] / fg = "...") — HUME treats the two as equivalent, though Helix itself reads only the flat form, so a section-header theme won't travel back.

A color can be a hex literal, a palette name you define, or one of the sixteen terminal color names Helix themes use (red, light-gray, and so on) — these resolve to fixed colors from the standard terminal palette rather than to whatever your own terminal happens to have those colors set to, so a theme looks the same everywhere and unfocused-pane dimming has an actual color to blend toward. A color value outside these three forms leaves that one entry unstyled rather than failing the whole load, and :messages names it.

One thing a Helix theme can contain isn't supported, but it doesn't stop the rest of the theme from loading either: the top-level rainbow array. HUME has no rainbow-bracket highlighting, so it has nothing to drive. The theme still loads, and the entry is reported in :messages like any other one HUME couldn't use.

A theme fails to load outright only when the problem is with the document rather than one entry in it: invalid TOML syntax, an inherits parent that doesn't exist or forms a cycle or nests more than eight deep, or an inherits/palette key that isn't a string/table. Loading then keeps your current theme.

Installing themes

To install a third-party theme repository, run :plum-install-theme <user/repo> (see Core Plugins → core:plum) — for example:

:plum-install-theme cvlmtg/everforest.hume

INFO

cvlmtg/everforest.hume is Everforest, ported from Helix — a green-based, low-contrast color scheme designed to feel warm and comfortable on the eyes, inspired by forest colors in fall.

:theme <Tab> picks it up right away, no restart needed.

A theme editor is available online — a single-file HTML tool you download and open in a browser to edit themes visually and export them as TOML: https://raw.githubusercontent.com/cvlmtg/HUME/main/tools/theme-editor/index.html

Theme scopes

A scope not listed below behaves as Helix's own theme reference describes it. Every syntax-highlighting scope works this way, so the part of a theme that colors your code carries over as-is.

Scopes HUME adds

These have no Helix equivalent:

  • ui.cursor.match.search — coloring every visible search match, falling back to ui.cursor.match when unset
  • ui.popup.scroll — scrollbar thumb on a scrolled hover popup (Helix only themes a scrollbar for ui.menu)
  • ui.window.focused — seam divider segments adjacent to the focused pane, falling back to ui.window
  • ui.drawer — background of the bottom drawer (show-drawer-list!), a generic pick-list panel Helix doesn't have
  • ui.statusline.search / .command / .sift — one more mode-tinted statusline scope per HUME mode Helix doesn't have, alongside Helix's own ui.statusline.normal/.insert/.select (.select colors Extend, HUME's name for what Helix calls Select mode; .sift colors HUME's own Sift mode — the s regex prompt)
  • ui.virtual.invisible — the <200b>-style stand-in for a character the terminal must not be shown as itself (see the note under Buffer options above)
  • diff.plus.line / diff.minus.line / diff.delta.line — the whole-line background tint core:git-diff paints for an added, deleted, or changed line (diff.minus.line also colors the ghost text of a deleted line, since nothing is left in the buffer to color). Falls back to nothing if left undefined — an unmodified Helix theme colors the gutter marker (below) but paints no row tint, which is the deliberate trade-off rather than a bug
  • diff.plus.word / diff.minus.word — word-level highlight inside a changed line (core:git-diff's inline diff), inside the row-level .line tint above
  • diagnostic.error.message / .warning.message / .info.message / .hint.message and their .message-text counterparts — the :messages log's severity badge and body text, a HUME-only feature
  • error.diagnostic.inline / warning.diagnostic.inline / info.diagnostic.inline / hint.diagnostic.inline — the diagnostic summary shown at the end of an offending line. Separate from diagnostic.error and friends, which style the squiggle under the code itself, so the summary doesn't pick up that scope's underline. Each falls back to the matching error/warning/info/hint gutter color when unset

Helix scopes HUME doesn't read

Declaring any of these has no effect today. They fall into two groups, and the difference matters if you're deciding whether to keep them in a theme you maintain.

Waiting on a feature. HUME doesn't have the thing these color yet. When it does, these scopes are the natural way to theme it, so leaving them in a theme costs nothing:

  • No debugger (DAP) support: ui.debug, ui.debug.breakpoint, ui.debug.active
  • No tabline yet: ui.bufferline, ui.bufferline.active, ui.bufferline.background
  • No which-key-style prompts: ui.popup.info, ui.help, ui.text.info
  • No picker-preview highlighting: ui.highlight, ui.highlight.frameline
  • No cursor-column ruler: ui.cursorcolumn, ui.cursorcolumn.primary, ui.cursorcolumn.secondary
  • No per-kind completion-entry styling: ui.text.directory, ui.text.symlink
  • No LSP deprecated/unnecessary diagnostic tags: diagnostic.deprecated, diagnostic.unnecessary
  • No move- or conflict-specific diff styling: diff.delta.moved, diff.delta.conflict
  • No snippet support: tabstop
  • Ruler columns, the soft-wrap indicator, virtual jump labels, and per-kind inlay hints: ui.virtual.ruler, ui.virtual.wrap, ui.virtual.jump-label, ui.virtual.inlay-hint.parameter, ui.virtual.inlay-hint.type

HUME's interface works differently. These color a piece of Helix's UI that HUME either doesn't present the same way or styles from another scope. They may never apply, so the listed alternative is where to put the color instead:

  • ui.picker.header, ui.picker.header.column, ui.picker.header.column.active — HUME's picker has no column headers to style
  • ui.gutter, ui.gutter.selected — the gutter takes no background of its own; style it with ui.linenr and ui.linenr.selected
  • ui.statusline.inactive, ui.text.inactive — HUME tints the whole statusline row by mode rather than dimming an unfocused one, and dims an unfocused pane wholesale instead of theming an inactive state
  • ui.cursorline.secondary — only the primary selection's line is tinted, via ui.cursorline.primary
  • ui.background.separator — HUME's prompt line has no separator rule beneath it

Scopes HUME reads differently

ui.window is the seam between split panes. HUME draws the divider glyph itself, so it reads that scope's foreground; a theme that sets only a background falls back to the theme's own base text color (ui.text) for it, the same fallback every other undecorated element uses.

ui.statusline.separator divides the statusline's segments. HUME tints the whole statusline row by mode, so leaving this scope undefined takes the row's own current color rather than the untinted ui.statusline — otherwise the separator would show through a mode-tinted row as a stripe of the wrong color. Set it explicitly and that wins, in every mode.

Key bindings

scheme
(bind-key! 'normal "ctrl-j" "move-down")
(bind-key! 'normal "g e" "goto-last-line")
(unbind-key! 'normal "ctrl-j")

bind-key! takes an editor command's name — the same names in Builtin Commands — never a typed command; there's no way to bind one of those to a key.

bind-key! — binds a key in the given mode ('normal, 'insert, 'extend). unbind-key! — removes a binding. bind-key-extend! — binds a key so it always extends the selection, as the one-shot Ctrl+ motions do.

To set several bindings at once, use the plural forms:

scheme
(bind-keys! 'normal
  ("ctrl-h" "select-prev-word")
  ("ctrl-l" "select-next-word"))

(bind-keys-extend! 'normal
  ("ctrl-n" "select-line")
  ("ctrl-y" "select-line-backward"))

(unbind-keys! 'normal "ctrl-j" "ctrl-k")

bind-keys! batches bind-key!, bind-keys-extend! batches bind-key-extend!, and unbind-keys! batches unbind-key! — each takes one or more (key cmd) pairs (or, for unbind-keys!, one or more bare keys) instead of a single one.

Binding a key that waits for a character

Some commands need a character typed right after the key (find/till motions, surround). bind-wait-char! binds a key sequence so the next keypress is captured and passed to the target command instead of being looked up in the keymap:

scheme
(bind-wait-char! 'normal "m s" "surround-add")

Inside the target command, read the captured character with (pending-char) — see Plugins for the full command-writing API, including the related (request-wait-char! cmd-name), which waits for a character from inside an already-running command rather than from a key binding.

Key-string grammar

A key string is a whitespace-separated list of tokens. Each token is [modifier-]*key where the modifier separator is a dash - (not +):

ComponentValues
Modifiersctrl-/c-, shift-/s-, alt-/a- (case-insensitive, repeatable, any order, short and long forms may be mixed)
Named keysspace, tab, enter / return / cr / ret, esc / escape, lt (<), backspace / bs, delete / del, insert / ins, home, end, pageup, pagedown, up, down, left, right, f1f12
Single charAny single Unicode character; case is preserved ("G" and "g" are distinct)

Multi-key sequences are space-separated: "g e", "m i w", "ctrl-p h". Examples: "ctrl-j", "shift-tab" (becomes BackTab), "ctrl-shift-left", "g e".

Binding the backslash key

In Scheme string literals \ is the escape character, so to bind the \ key write it escaped — "\\", not "\":

scheme
(bind-key! 'normal "\\" "my-command")

The same applies to the double quote: bind " as "\"".

Statusline

The statusline is fully configurable from Scheme:

scheme
(configure-statusline! '("Mode" "Separator" "FileName") '() '("Position"))

Each argument is a list of element name strings: left, center, right.

Available elements:

ElementDescription
"Mode"Current mode label (NOR/INS/EXT/CMD/SRC/SIF)
"Separator"Divider between sections
"FileName"Current buffer filename (basename)
"FilePath"Full path of current buffer
"Cwd"Working directory
"Position"Line and column position — column counts graphemes (h/l presses), matching :diagnostics and goto/references lists
"KittyProtocol"Kitty keyboard protocol indicator
"DirtyIndicator"[+] when buffer has unsaved changes
"LineEnding"Line ending type (LF/CRLF)
"SearchMatches"Current search match count
"MiniBuf"Pending key sequence hint
"MacroRecording"Macro recording indicator
"Language"Buffer language
"ReadOnly"[RO] indicator
"Diagnostics"Error and warning counts from the language server

The default is equivalent to:

scheme
(configure-statusline!
  '("Position" "FilePath" "Language" "ReadOnly" "DirtyIndicator")
  '()
  '("MacroRecording" "SearchMatches" "Diagnostics" "KittyProtocol" "Separator" "Mode"))

Custom elements

Place "steel:<name>" for any <name> of your choosing to add your own element. <name> must be non-empty and must not contain , or |. Push its text with set-statusline-text!, driven by whatever should trigger an update (a hook, a timer, a command):

scheme
(configure-statusline! '("steel:line-count" "FilePath") '() '("Position" "Mode"))

(define (refresh-line-count! bid)
  (set-statusline-text! "line-count" bid
    (string-append (number->string (length (buffer-lines bid))) "L")))

(register-hook! 'on-text-changed refresh-line-count!)
(register-hook! 'on-buffer-enter refresh-line-count!)

core:git-diff ships a "steel:git-branch" element using this same mechanism — see Core Plugins → core:git-diff — just add it to your own configure-statusline! call.

set-statusline-text! takes the element name, a buffer id, and the text to show; an empty string clears it. Each buffer keeps its own value per name, and a placed element shows only the focused buffer's — switching to a buffer with nothing pushed yet shows nothing, same as any other element with no content. Placing the element and pushing its text are independent — either can happen first, and neither errors if the other hasn't happened yet.

Language detection

HUME detects file languages from extension, glob pattern, or shebang line. See Teach HUME a new language for defining custom languages with define-language! and, for grammars outside the catalog, register-grammar!.

Example init.scm

A complete starting config — copy it to ~/.config/hume/init.scm and edit:

scheme
;; Bundled plugins
(load-plugin "core:stdlib")           ; helper toolkit other plugins depend on
(load-plugin "core:pickers")          ; fuzzy file/buffer finders
(declare-plugin "core:lsp")           ; language server features
(declare-plugin "core:plum")          ; plugin/grammar manager

Before your init.scm runs, HUME loads its own prelude (which defines bind-keys!, define-language! and friends) and its built-in language definitions — so those are always available to you.

File locations

HUME resolves its directories per OS:

PathmacOS / LinuxWindows
Config dir (init.scm, hand-authored themes/)$XDG_CONFIG_HOME/hume/ (default ~/.config/hume/)%APPDATA%\hume\
Data dir (plugin clones, tree-sitter grammars, installed themes/)$XDG_DATA_HOME/hume/ (default ~/.local/share/hume/)%LOCALAPPDATA%\hume\ (fallback %APPDATA%\hume\)
Runtime dir (bundled runtime/: tutor.rst, themes/, scheme/, init.scm.example, core plugins)see belowsee below

--config <FILE> overrides only which file HUME evaluates as init.scm — user themes/ and the data dir still resolve from the config dir above regardless.

HUME looks for its runtime directory in this order, taking the first that exists:

  1. $HUME_RUNTIME, if set
  2. ../share/hume/ relative to the binary (macOS and Linux only — this is the layout you get from the release archive)
  3. runtime/ next to the binary (the Windows archive layout)
  4. runtime/ in the current working directory (handy when running from a source checkout)

Notable subpaths inside the data dir: data/plugins/ (PLUM-managed plugin clones), data/grammars/ and data/grammars/sources/ (compiled and source tree-sitter grammars), data/themes/ (installed third-party themes).

Plugins are trusted code

Plugins run with the same privileges as HUME itself — they can read and write any file your user account can, and run other programs. There is no sandbox. Install third-party plugins only from sources you trust.

INFO

On macOS HUME follows the XDG convention (~/.config/hume/, ~/.local/share/hume/) rather than ~/Library/Application Support/.

Released under the MIT License.