Skip to content

feat: forward expr/silent/noremap/replace_keycodes on KeySpec#19

Merged
zuqini merged 3 commits intomainfrom
feat/keyspec-passthrough
May 7, 2026
Merged

feat: forward expr/silent/noremap/replace_keycodes on KeySpec#19
zuqini merged 3 commits intomainfrom
feat/keyspec-passthrough

Conversation

@zuqini
Copy link
Copy Markdown
Owner

@zuqini zuqini commented May 7, 2026

Addresses #18.

What

Forward four vim.keymap.set boolean options through KeySpecapply_keys so they aren't silently dropped:

Refactors keymap.M.map to take an opts table instead of a positional arg list, and uses a SUPPORTED_OPTS whitelist to keep forwarding closed over future additions.

Why

Migrating a keys = { ... } block from lazy.nvim should preserve behavior. Before this change, expr = true, silent = true, and friends were quietly ignored — the keymap was still installed, but without the requested attribute, which is a surprising failure mode for new users.

Edge cases handled

  • noremap alias. vim.keymap.set explicitly does not support noremap and derives it from remap. Passing noremap straight through silently does nothing. keymap.lua translates noremapremap before forwarding, so noremap = false actually produces a remappable keymap. Explicit remap wins when both are provided.
  • Lazy-trigger proxy stays non-expr. The proxy in lazy_trigger/keys.lua runs a Lua closure that loads the plugin then nvim_feedkeys-replays the original lhs — it must not be an expr mapping itself. The proxy forwards desc/mode/nowait/silent/remap/noremap so the first (proxy) press matches subsequent presses through the real keymap; expr and replace_keycodes are the only omissions, since the proxy's rhs is a Lua callback returning nil and making it expr would feed nil keys.
  • replace_keycodes default. Documented to match Neovim's actual behavior (defaults to true when expr is true), not the previously-claimed false.

Tests

tests/lazy_keys_test.lua adds coverage for:

  • expr=true forwarded to vim.keymap.set (attribute check + end-to-end feedkeys round-trip)
  • silent=true forwarded
  • noremap=true forwarded
  • noremap=false produces a remappable keymap (regression test for the alias translation)
  • remap=true, noremap=true resolves to remappable (explicit remap wins)
  • replace_keycodes=true forwarded with expr
  • Lazy proxy keymap is not expr

All 322 tests pass.

Files

Path Change
lua/zpack/keymap.lua Opts-table refactor + noremap alias translation
lua/zpack/lazy_trigger/keys.lua Update proxy call to new M.map signature
lua/zpack/types.lua Annotate new KeySpec fields
doc/zpack.txt, docs/spec.md Document new fields and correct replace_keycodes default
tests/lazy_keys_test.lua Coverage for forwarded options and alias edge cases

Addresses #18. Lazy.nvim-style KeySpec entries that set expr=true (and
related boolean opts) were silently dropped because keymap.map only
forwarded desc/remap/nowait. Refactor M.map to an opts table and plumb
all four standard vim.keymap.set booleans through apply_keys so
migrations from lazy.nvim work without surprise.

vim.keymap.set ignores noremap and derives it from remap, so translate
the lazy.nvim noremap alias into remap before forwarding — otherwise
noremap = false would silently still produce a non-remappable keymap.

The lazy-trigger proxy mapping intentionally stays non-expr: it must
feed the original lhs through nvim_feedkeys so the real (post-load)
expr mapping fires.
@zuqini zuqini force-pushed the feat/keyspec-passthrough branch from 80272f7 to 551cd25 Compare May 7, 2026 19:50
zuqini added 2 commits May 7, 2026 13:13
Per code-review on #19, the proxy installer in lazy_trigger/keys.lua
forwarded desc/mode/nowait/silent but omitted remap, leaving the
proxy mapping inconsistent with the post-load real keymap when the
user's KeySpec set remap=true (or noremap=false). The user's intent
should apply to every press, not just post-load — `<plug>`-style
remap chains and inspection tools alike read the proxy attributes.

Forward remap with the same noremap→remap alias translation that
apply_keys performs. expr/replace_keycodes remain the only deliberate
omissions — the proxy's rhs is a Lua callback returning nil, so an
expr proxy would feed nil keys and the plugin would never load.

Tests:
- replace_keycodes=false explicit override survives the expr-implied
  default mirror in keymap.lua (regression test for the `== nil`
  check that gates the auto-default).
- Lazy proxy reflects remap=true and translates noremap=false.
- Post-load handoff: pressing the proxy lhs deletes the proxy, runs
  process_spec, and installs the real expr=true keymap.
vim.keymap.set raises "replace_keycodes requires expr" if the option
is forwarded without expr. The SUPPORTED_OPTS pass-through made a
KeySpec like { lhs, rhs, replace_keycodes = true } crash apply_keys.
Gate the forward on expr inside M.map.

The noremap→remap alias translation was duplicated byte-for-byte in
keymap.apply_keys and lazy_trigger/keys.lua. Move it into M.map so
there is one source of truth, and let apply_keys pass the KeySpec
directly (the SUPPORTED_OPTS whitelist filters [1]/[2]/mode out).

Tests:
- KeySpec drops replace_keycodes when expr is unset (regression).
- Lazy proxy load handoff: post-load real keymap forwards silent and
  the noremap alias (covers the plugin_loader → apply_keys path that
  the existing eager `lazy = false` tests don't exercise).
@zuqini zuqini merged commit 8d02f50 into main May 7, 2026
2 checks passed
@zuqini zuqini deleted the feat/keyspec-passthrough branch May 7, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant