grammar

Direction: guest calls into the host through it · Capability: none (pure data / dispatch) · Worlds: auto-pair-plugin (imports), comment-plugin (imports), grammar-plugin (imports), project-plugin (imports), treesitter-context-plugin (imports)

The grammar-extension API (plugin-host.md §4.1, PH7.7). This is the surface a plugin calls to contribute new vim grammar — register_{motion,operator,text_object,ex_command,action} — mirroring the native CommandRegistry::register_* imperative API. The host provides these functions; the guest imports them and calls them (from its register-grammar export). Each records the contribution into PluginState; after register-grammar returns, the host builds a native *Spec with a trampoline apply stamped SourceLayer::Plugin(id) and registers it into the SAME CommandRegistry a builtin lives in (PH7.7c) — so a plugin command is indistinguishable from a builtin to the dispatcher (paramount #3).

The grammar handling (dispatcher, :-line + chord parser, operator∘motion composition, ranges, counts, registers) stays native, sync, and untouched; a plugin only adds entries here. spec carries the metadata; the behavior is a guest export in grammar-callbacks, dispatched by a guest-chosen callback id (the PH7.3d trampoline pattern). Registration returns nothing — the guest dispatches by its own callback, and the host stamps the CommandId / provenance (a plugin cannot forge either, §6).

Uses

Functions (5)

register-action

register-action: func(name: string, doc: string, spec: action-spec, callback: u32)

Contribute a chord-bound action. callback → grammar-callbacks.apply-action.

Example — Register one action per chord from a table, each with its own callback id · plugins/auto-pair/src/lib.rs

let spec = || ActionSpec {
    args_schema: Vec::new(),
};
for (name, doc, cb) in [
    ("auto-pair-open-round", "insert ()", CB_OPEN_ROUND),
    ("auto-pair-open-square", "insert []", CB_OPEN_SQUARE),
    ("auto-pair-open-curly", "insert {}", CB_OPEN_CURLY),
    ("auto-pair-close-round", "step over )", CB_CLOSE_ROUND),
    ("auto-pair-close-square", "step over ]", CB_CLOSE_SQUARE),
    ("auto-pair-close-curly", "step over }", CB_CLOSE_CURLY),
    ("auto-pair-quote-double", "pair \"\"", CB_QUOTE_DOUBLE),
    ("auto-pair-quote-single", "pair ''", CB_QUOTE_SINGLE),
    ("auto-pair-quote-backtick", "pair ``", CB_QUOTE_BACKTICK),
    (
        "auto-pair-close-manual",
        "close the nearest unmatched opener in scope (manual style)",
        CB_CLOSE_MANUAL,
    ),
    (
        "auto-pair-backspace",
        "delete an empty pair, else fall through to normal backspace",
        CB_BACKSPACE,
    ),
] {
    grammar::register_action(name, doc, &spec(), cb);
}

register-ex-command

register-ex-command: func(name: string, doc: string, spec: ex-command-spec, parse-callback: u32, apply-callback: u32)

Contribute an ex-command. TWO callbacks — parse-callback → grammar-callbacks.parse-ex-args (the : line's rest → typed args), apply-callback → grammar-callbacks.apply-ex-command.

Example — Register an argument-less ex-command with its parse and apply callbacks · plugins/project/src/lib.rs

lattice::plugin_host::grammar::register_ex_command(
    "project-switch",
    "Choose a project, then act on it. The verb this whole plugin \
     exists for: every other project-aware surface roots itself at the \
     buffer you are standing in, which is right until you want the one \
     you are not.",
    &ExCommandSpec {
        latency_class: LatencyClass::Reflex,
        accepts_bang: false,
        accepts_range: false,
        args_schema: Vec::new(),
        surface_form: SurfaceForm::Keyword,
    },
    CB_PARSE,
    CB_SWITCH,
);

register-motion

register-motion: func(name: string, doc: string, spec: motion-spec, callback: u32)

Contribute a motion. callback is the id the host passes back to grammar-callbacks.apply-motion on dispatch.

Example — Register a linewise, non-jump motion answered by callback 1 · crates/lattice-plugin-host/tests/fixtures/grammar-guest/src/lib.rs

grammar::register_motion(
    "down-n",
    "jump count lines down (fixture)",
    &MotionSpec {
        jump: false,
        exclusive: false,
        args_schema: Vec::new(),
    },
    1,
);

register-operator

register-operator: func(name: string, doc: string, spec: operator-spec, callback: u32)

Contribute an operator. callback → grammar-callbacks.apply-operator.

Example — Register an operator with its own chord (gc, doubled gcc) · plugins/comment/src/lib.rs

grammar::register_operator(
    "comment-toggle",
    "toggle line comments over the operated range",
    &OperatorSpec {
        repeatable: true,
        args_schema: Vec::new(),
        // `false`, and load-bearing: a blockwise `<C-v>` selection
        // arrives as ONE contiguous range rather than per row, like
        // `>` / `gU` and unlike `d` / `y`. Rule 1 is a property of the
        // range — decided per row, a mixed block inverts. See
        // `toggle::tests::a_mixed_block_must_be_decided_as_one_range`.
        blockwise_per_row: false,
        post_motion_char: false,
        // CM.2: the chord travels with the operator. `doubled` is the
        // TRAILING key, so this is `gcc` — the spelling commentary and
        // Neovim use — rather than `gcgc`.
        chord: Some("gc".to_string()),
        doubled: Some("c".to_string()),
    },
    CB_TOGGLE,
);

register-text-object

register-text-object: func(name: string, doc: string, spec: text-object-spec, callback: u32)

Contribute a text object. callback → grammar-callbacks.apply-text-object.

Example — Register a text object answered by callback 2 · crates/lattice-plugin-host/tests/fixtures/grammar-guest/src/lib.rs

grammar::register_text_object(
    "to-cursor",
    "line start to cursor (fixture)",
    &TextObjectSpec {
        args_schema: Vec::new(),
    },
    2,
);