Skip to content

Repository files navigation

ui-utils.hx

Shared UI functions for my Helix plug-ins (juju, nrepl.hx): overlay geometry, frame and row drawing, key-event predicates, scroll math, a scrollable overlay-view shell, a transient menu, and a filterable list picker, built on top of Steel’s helix/components.

There are some /examples, but for real world use check the plug-ins above.

Probably vendor this if you want to use it in a plug-in of your own: high likelihood of breaking changes, at least until Steel is in a stable Helix release.

Installation

Plugins declare it as a forge dependency in their cog.scm:

(define dependencies
  '((#:name "ui-utils.hx"
     #:git-url "https://github.com/waddie/ui-utils.hx")))

Forge installs it to ~/.steel/cogs/ui-utils.hx/ alongside the plugin. For a manual install from a checkout:

./install.sh

Consuming from a plugin

(require "ui-utils.hx/strings.scm")
(require "ui-utils.hx/keys.scm")

Modules are documented in their headers. The pure modules (strings, scroll, nav, selection, line-edit, menu-model, picker-model, geometry-model, component) have no Helix dependencies and load under the bare steel CLI; the rest (geometry, keys, style, draw, overlay-view, menu, picker) require helix/components and only load inside Helix.

The -model modules hold the logic their component shell would otherwise bury behind helix/components: geometry-model is the overlay and pane arithmetic that geometry wraps in area structs, and picker-model owns make-picker and the picker spec so that spec construction stays testable.

There is no library-level mutable state: styles and overlay scale are passed per call (a tag->style function, an #:overlay-scale thunk).

Preview lines

A picker's #:preview closure returns one entry per line. A line is either a (text . style) pair, styled as a whole, or a list of such pairs drawn left to right as spans:

;; one style for the line
(cons "plain text" (style))

;; several styles across the line
(list (cons ":deps " (style-fg (style) Color/Magenta))
      (cons "{}"     (style)))

'() draws a blank line. Span lines are clipped to the pane width without an ellipsis, since the cut falls inside one span's styling. See examples/picker-preview.scm.

Row spans

A row given to draw-rows (and so to an overlay-view) is normally one string under one style tag:

(hash 'text "  modified src/main.rs" 'tag 'file)

A row needing several styles on one line declares columns instead:

(hash 'spans
      (list (hash 'text "  12 " 'tag 'diff-context 'width 5)
            (hash 'text old-line 'tag 'diff-del    'flex 1)
            (hash 'text ""      'tag 'diff-meta   'width 1)
            (hash 'text new-line 'tag 'diff-add    'flex 1)))

'width fixes a column; 'flex takes a weighted share of what is left (a span with neither flexes with weight 1). layout-spans (in geometry-model) resolves the positions at draw time, where the width is known, so a caller can build rows without it. Fixed columns that overflow the row clamp the rest to zero width. Cursor and marked styling apply per span, so the highlight still covers the whole line. The two row shapes can be mixed in one list.

The filter line

Under #:filter? #t the line is editable, same keys as the builtin pickers:

Key Action
Left, C-b back a character
Right, C-f forward a character
Alt-b, C-Left back a word
Alt-f, C-Right forward a word
C-a / C-e line start / line end
Backspace delete before the caret
Delete delete at the caret
C-w, Alt-Backspace delete the previous word
Alt-d, Alt-Delete delete the next word
C-k kill to end of line

The caret arithmetic is line-edit.scm, which is pure and reusable by any other one-line input.

Column filters

A columned picker can opt into the query syntax Helix's own pickers use, where %name sends the text after it to one column (in the builtin command palette, %b ? searches the bindings column for ?):

(make-picker #:columns columns
             #:filter? #t
             #:filter-columns? #t
             #:primary-column 0   ; the default
             ...)

Bare text filters the primary column. %name picks the column whose label name prefixes, ignoring case, shortest label first when several match, so %t fn narrows a "Type" column and %ns core a "Namespace" one. A space ends the name, which leaves multi-word labels reachable only by a prefix. Text under an unrecognised name falls back to the primary column, \% is a literal percent, and naming a column twice concatenates its patterns.

Only the primary column ranks the result: the other columns narrow it while keeping the order the items were given in. #:filter-fn still overrides the whole thing. Off (the default), % is ordinary filter text. See examples/picker-columns.scm.

Development

sh tests/run-all.sh

Runs the headless unit tests for the pure modules. Each tests/test-*.scm file is a steel-test suite covering one module; run-all.sh runs them all in file mode and aggregates the exit code.

Requires steel on PATH and steel-test installed in ~/.steel/cogs (via forge pkg install --git https://github.com/waddie/steel-test, or copy the package there by hand). Run individual suites with steel tests/test-nav.scm.

Drawing and component modules cannot load outside Helix and are verified live.

License

Copyright © 2026 Tom Waddington

Distributed under the MIT License. See LICENSE file for details.

About

Shared UI functions for my Helix plug-ins

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages