pmacs/builtin/commands
Levi Neuwirth 6844262495 feat(config): typed configuration registry with buffer-local scope
A third registry beside CommandRegistry and HookRegistry, per
docs/config-registry-framing.md. Unblocks the per-buffer auto-pair
toggle, the first of the five backlog items the missing config surface
was gating.

Substrate (src/config_registry.rs):

  * ConfigRegistry keyed by name with definition order preserved, R42
    mandatory descriptions, R50 typo detection, duplicate rejection,
    and SourceLocation provenance -- the command/hook vocabulary.
  * Closed scalar kinds: boolean, integer, number, string, enum. Owned
    Rust values; Lua tables, functions and userdata are never stored.
    Integer exactness is checked by value, never math.type, so the
    luajit and lua54 builds agree.
  * Two scopes. get(name, buf) resolves buffer-local -> global ->
    default; get(name) with no buffer resolves the global chain only
    and never consults an ambient buffer. Buffer-locals live in a
    registry-owned side table purged at after_buffer_removed, beside
    the keymap purge already there.
  * An override is ALWAYS stored, even when equal to the value it
    shadows; only value_epoch and listener dispatch key on effective
    change. Without this a buffer pinned to the current value stores
    nothing and a later global set flips it -- the pin silently never
    existed. equal_valued_local_override_is_still_stored_and_shields_buffer
    fails against the naive reading.

Bindings (src/lua_bindings/config.rs):

  * define/get/set/set_local/reset/is_set/describe/list/on_change.
    Spec tables are read raw, so neither an unknown key nor a
    metatable-provided value can smuggle a field in.
  * Listeners commit inside the borrow, snapshot, drop the borrow, and
    only then re-enter Lua -- verified by holding the borrow and
    watching the test panic with "RefCell already borrowed". A raising
    listener is logged without blocking later ones or rolling back, and
    a depth bound turns an accidental cycle into a pointed error.
    Listeners persist until explicitly disposed; there is no Gc path,
    matching the rest of the codebase.
  * StartupOnly freezes off the existing InitCompleteFlag at write
    time, so this arc adds no editor.rs call at all.

Adopters, each defining its own key so SourceLocation names the owning
module: editing.auto-pair (pair.lua, read per-buffer against the typed
edit's SOURCE buffer), editing.trim-on-save (editops.lua),
autosave.interval-ms (autosave.lua). No public function is removed or
deprecated, and both migration wrappers keep their legacy coercion --
trim_on_save("yes") still enables, interval_ms(1500.7) still floors to
1500 -- coercing before handing the strict registry a conforming value.

M-x describe-setting renders into *help*, modeled on describe-command.

Framing revision 3 records four defects implementation found in the
document itself: acceptance 30 and 31 contradicted each other; the
planned builtin/runtime/config.lua had nothing to hold and would have
broken the source-location contract had it held the one helper it might
have; F5 asked define to police a call it cannot see, moved to
set_local; and list() ordering was underspecified.

No protocol change; SUPPORTED stays [6..18]. No wire surface. Zero
changes to src/editor.rs.

Gates: fmt, clippy -D warnings, --lib (1683), --lib --features crdt
(1857), the new config_registry_acceptance (13) plus auto_pair (45),
editops (72), autosave (29) and m9_6 (25), m4 --skip basedpyright
(114), PMACS_REQUIRE_GPU=1 pmacs-gpu (109), the lua54 backend build,
and the full workspace sweep (2795 tests, exit 0). git diff --check
clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 18:29:18 -04:00
..
default.lua feat(config): typed configuration registry with buffer-local scope 2026-07-21 18:29:18 -04:00