NORMAL ~/wkd/p/lib/help/lib.nvim-composer :set skin=modern utf-8

lib.nvim-composer.txt

Subcommand user commands with completion and docgen — lib.nvim

doc/lib.nvim-composer.txt — rendered from the plugin's own vimdoc

*lib.nvim-composer.txt*    Subcommand user commands with completion and docgen

lib.nvim.bindings.usercmd.composer                              *lib.nvim-composer*

Compose a declarative route spec into ONE Neovim user command with
subcommands, <Tab> completion, and Markdown docs — all read from the same
route tree, so behavior and docs can never drift. Turns the
:VerbFeatureA / :VerbFeatureB anti-pattern into :Verb feature-a /
:Verb feature-b, where {Verb} is the central action (:Replace, :File).

CONTENTS *lib.nvim-composer-contents*

  1. Usage ..................................... |lib.nvim-composer-usage|
  2. Context ................................... |lib.nvim-composer-ctx|
  3. Argument types ............................ |lib.nvim-composer-argtypes|
  4. Flags ...................................... |lib.nvim-composer-flags|
  5. Bare key=value ............................. |lib.nvim-composer-kv|
  6. Buffer-local commands ...................... |lib.nvim-composer-buffer|
  7. Count prefix ............................... |lib.nvim-composer-count|
  8. Documentation generation .................. |lib.nvim-composer-docgen|
  9. Functions ................................. |lib.nvim-composer-functions|

1. USAGE *lib.nvim-composer-usage*

    local composer = require("lib.nvim.bindings.usercmd.composer")

    composer.verb("Replace", {
      desc    = "Text replacement operations",
      default = function(ctx) require("myplugin").prompt() end,  -- bare :Replace
      routes  = {
        { path = { "buffer" }, desc = "Replace in the current buffer",
          run = function(ctx) require("myplugin").buffer() end },
        { path = { "surround" },
          args = {
            { name = "kind",   type = "STRING", enum = { "quote", "paren" } },
            { name = "target", type = "STRING" },
          },
          desc = "Wrap TARGET with KIND surroundings",
          run  = function(ctx)
            require("myplugin").surround(ctx.args.kind, ctx.args.target)
          end },
      },
    })
CDX: replacer.nvim's real :Replace/:Surround registration (command.lua,
surround.lua) does use this module, but only single path = {} root
routes that forward ctx.raw into its pre-existing parser -- it never
declares a buffer/surround subcommand tree. Judgment call whether the
USAGE example should instead be trimmed straight from that real
registration (less illustrative of subcommand routing) or stay a
require("myplugin") stand-in (current fix: no longer implies a
replacer.nvim API that doesn't exist).
:Replace <Tab> completes buffer | surround; :Replace surround <Tab>
completes quote | paren; bad input is reported with the route's usage.

A fluent form is also available (:build() registers):
    composer.verb("Replace")
      :desc("…")
      :route("surround", { args = {...}, run = function(ctx) end })
      :build()

2. CONTEXT *lib.nvim-composer-ctx*

Each run receives a context table:

    ctx.args    coerced positional args, keyed by ArgSpec.name
    ctx.pos     coerced positional args, in order
    ctx.flags   coerced --flag/-x values, keyed by FlagSpec.name
    ctx.kv      coerced bare key=value pairs, keyed by KvSpec.key
    ctx.rest    leftover tokens beyond the declared schema
    ctx.path    the literal path that matched, e.g. { "surround" }
    ctx.bang    true when invoked as :Verb!
    ctx.range   { line1, line2, count, range }
    ctx.raw     the untouched nvim callback args

3. ARGUMENT TYPES *lib.nvim-composer-argtypes*

Each type carries validation and completion. Built-ins: STRING, INT, FLOAT,
BOOL, PATH, DIR, FILE, BUFFER, WINDOW, plus enum = {...} on any ArgSpec (closed set,
case-insensitive, completed from the members). Coercion reuses
lib.nvim.normalize validators; PATH/DIR/FILE completion uses Neovim's own
file completion. Register a custom type once:
    composer.register_type("HIGHLIGHT_GROUP", {
      validate = function(raw) return true, raw, nil end,
      complete = function(lead) return vim.fn.getcompletion(lead, "highlight") end,
    })
run may also be a module-path string, required lazily on first dispatch.

4. FLAGS *lib.nvim-composer-flags*

A route may declare flags, parsed out of its token tail before positional
binding — modeled on replacer.nvim's BOOL_FLAGS/VALUE_FLAGS split. Strictly
opt-in: a route with no flags behaves exactly as before a leading "--" in a
positional value is never special-cased unless the route declares flags.
    composer.verb("Replace", {
      routes = {
        -- path = {} is the verb's ROOT route: it matches even with no
        -- literal subcommand, reproducing replacer.nvim's flat grammar
        -- :Replace {old} {new} [scope] [--flags] verbatim.
        { path  = {},
          args  = { { name = "old", type = "STRING" }, { name = "new", type = "STRING" } },
          flags = {
            { name = "dry",    bool = true },
            { name = "type",   type = "STRING", repeatable = true },
            { name = "engine", type = "STRING", enum = { "fzf", "telescope" } },
          },
          run = function(ctx) require("replacer").run(ctx.args, ctx.flags) end },
      },
    })
Flags may appear anywhere in the tail — before, after, or between positionals
(:Replace --dry foo bar and :Replace foo bar --dry are equivalent) — and a
literal "--" stops flag parsing (matches replacer.nvim's flags_done
sentinel). A value flag accepts --name=value or --name value. An
undeclared --name is a hard error, not silently treated as positional.
<Tab> completes flag names and, after --name=, the flag's own value
completer — derived from the same FlagSpec used for dispatch.

Known limitation: completion is ambiguous for a verb mixing a path = {}
root route with sibling subcommand routes (an unusual combination). Dispatch
is unaffected; only the <Tab> suggestion in that mixed case can be
unhelpful.

Short-flag aliases (-x)

A flag may declare a single-char short alias:
    flags = {
      { name = "replace", short = "r", bool = true },    -- --replace or -r
      { name = "output",  short = "o", type = "STRING" },-- --output=<v> or -o <v>
    }
-x and --name are interchangeable and may be mixed in one call
(:Recommend query -r --output=out.txt). Short flags only take their value
from the NEXT token (-o file.txt), never -o=file.txt. An unrecognized -x (no
short matches) is left as an ordinary positional rather than an error --
unlike --name, a bare "-" collides too easily with real values (a negative
number, a passthrough CLI arg). <Tab> after a bare "-" completes every
declared short flag.

5. BARE KEY=VALUE *lib.nvim-composer-kv*

A route may separately declare kv, for grammars like
":Diff target=file.lua view=vsplit" (no -- / - prefix at all):
    composer.verb("Diff", {
      routes = {
        { path = {},
          kv = {
            { key = "target", type = "STRING" },
            { key = "view", type = "STRING", enum = { "vsplit", "split" }, default = "vsplit" },
          },
          run = function(ctx)
            -- ctx.kv.target, ctx.kv.view (defaults applied when omitted)
          end },
      },
    })
Unlike flags, an UNDECLARED key=value-shaped token is left as an ordinary
positional rather than an error -- "=" shows up in too many legitimate
positional values (URLs, passthrough env assignments, ...) to treat every
match as intentional; only a token whose key matches a declared KvSpec.key is
ever consumed. <Tab> offers "key=" for every declared key, and value
completion after "key=" -- kv tokens have no marker prefix, so these
candidates are merged alongside whatever else is valid at that slot (a
subcommand name, a positional arg's own completions, ...), not used
exclusively. flags and kv compose freely on the same route (ctx.flags and
ctx.kv are both populated; parsing runs flags first, then kv, then whatever's
left binds to args).

6. BUFFER-LOCAL COMMANDS *lib.nvim-composer-buffer*

spec.buffer = true (current buffer) or an explicit bufnr registers via
nvim_buf_create_user_command instead of the global
nvim_create_user_command -- for per-buffer commands like a markdown preview's
:TableView, typically called from a FileType autocmd:
    vim.api.nvim_create_autocmd("FileType", {
      pattern = "markdown",
      callback = function()
        composer.verb("TableView", { buffer = true, routes = { ... } })
      end,
    })
Re-registering (e.g. the autocmd firing again for the same buffer) is safe --
nvim_buf_create_user_command overwrites like the global form does. The
fluent builder has the matching :buffer(v) method.

7. COUNT PREFIX *lib.nvim-composer-count*

spec.count = 0 (or any route's route.count) accepts a ":N Verb" count
prefix, the same shape as nvim_create_user_command's own count option --
the number becomes the default ctx.range.count when the prefix is
omitted:
    composer.verb("File", {
      count = 0,
      routes = {
        { path = { "next" }, run = function(ctx)
            cycle.navigate("next", ctx.range.count > 0 and ctx.range.count or 1)
          end },
      },
    })
    -- :File next      -> ctx.range.count == 0
    -- :3File next     -> ctx.range.count == 3
Like bang/range, nvim_create_user_command has one count slot per command,
not one per route -- an explicit spec.count wins; otherwise the first route
that declares count sets it. The fluent builder's :count(v) defaults to 0
when called with no argument.

8. DOCUMENTATION GENERATION *lib.nvim-composer-docgen*

The route tree drives docs too:
    handle:document()                 -- one verb  -> docs/BINDINGS/Usercmds.md
    composer.document()               -- every verb -> default path
    composer.document("docs/CMDS.md") -- explicit path
composer.setup({ docs = { path = "...", mode = "replace"|"section" } }) sets
the default path and mode. "section" updates a delimited
<!-- lib.nvim:composer --> … <!-- /lib.nvim:composer --> block so
hand-written prose survives regeneration; "replace" overwrites the file.

9. FUNCTIONS *lib.nvim-composer-functions*

composer.verb({name}, {spec?})                       *lib.nvim-composer-verb*
    Register a verb. With {spec} it registers immediately and returns a
    handle; without it, returns a fluent builder (call :build()).

composer.document({path?})                       *lib.nvim-composer-document*
    Write Markdown docs for every registered verb. Returns ok, err.

composer.setup({opts?})                             *lib.nvim-composer-setup*
    Configure docs defaults: { docs = { path, mode } }.

composer.register_type({name}, {def})       *lib.nvim-composer-register_type*
    Register a custom argument type { validate, complete? }.

composer.registry()                              *lib.nvim-composer-registry*
    The name->handle map of every verb built in this process.

composer.check_all()                            *lib.nvim-composer-check_all*
    Pre-flight check for every verb registered in this process, keyed by
    name.

composer.checkhealth({name_or_handle})       *lib.nvim-composer-checkhealth*
    Report one verb's route checks through vim.health. Call this from
    your plugin's own health.lua -- composer cannot register a
    discoverable :checkhealth target on your behalf.

composer.notify_check_all()            *lib.nvim-composer-notify_check_all*
    Notification-based alternative to checkhealth, for plugins with no
    health.lua to hook into. Returns ok_overall.

Access: require("lib.nvim.bindings.usercmd.composer") (direct),
require("lib").composer, or require("lib").usercmd.composer.

keys

j / k
next / previous line
gg / G
first / last line
⏎
open the line under the cursor
/
search the plugins
:
command line — Tab completes
:help x
vimdoc of a plugin (:e x = plugin page)
:ls · :log · :stack
plugin list · activity stream · dependency graph
:colo x
colorscheme
:set skin=
modern | tui
?
this help
esc
close