completion-source
Direction: guest implements this interface · Capability: none (pure data / dispatch) · Worlds: completion-source-plugin (exports)
Mirrors lattice_completion completion sources (PH7.6). A WASM completion source exports this interface; the host drives its async generate off the keystroke path (the LSP-async-completion precedent, pipeline.rs match_and_rank "pre-supplies rows from async LSP responses") and feeds the produced candidates through the native matcher / ranker / annotator.
Generator only, by design (option A, locked with Dhruva). The four native traits — Candidate{Generator,Matcher,Ranker,Annotator} — are NOT four guest exports: matches + annotate run per candidate on the synchronous keystroke pipeline, so crossing them to an async, actor-bound guest per item would fire hundreds of boundary calls per keystroke (paramount #1). The plugin's value-add is the GENERATOR (async produce, like LSP); matching / ranking / annotation stay native (they have good defaults a plugin rarely overrides — "the API grows from real plugins", design §5.5). The matcher / ranker / annotator data types are still mirrored in types.wit so the WIT is sized against the whole trait set before the ABI freeze.
Uses
completion-source-specfromtypesgenerate-contextfromtypesraw-candidatefromtypes
Functions (2)
generate
generate: func(ctx: generate-context) -> result<list<raw-candidate>, string>
Produce raw candidates for the current slot. ctx carries the query prefix + case flag (§4.2 owned projection); the host then runs the native match_and_rank over the result. Async — a produce call suspends the guest, never the keystroke path. An err string is logged and the source contributes no rows (the LSP-failure precedent). Candidates carry plugin-specific data via the candidate-data.extension hatch.
Example — Return the full candidate set; the host's native matcher filters it by the query · crates/lattice-plugin-host/tests/fixtures/completion-guest/src/lib.rs
fn generate(_ctx: GenerateContext) -> Result<Vec<RawCandidate>, String> {
// Return the full keyword set; the native matcher filters against the
// query prefix (matching stays native — option A). Each candidate uses
// the `extension` data hatch (kind-id 1, empty payload) — the plugin
// candidate-data path.
Ok(["alpha", "alphabet", "beta", "gamma"]
.iter()
.map(|w| RawCandidate {
insert_text: None,
text: w.to_string(),
display: w.to_string(),
source: Some("keywords".to_string()),
kind: CandidateKind::Plain,
data: CandidateData::Extension(CandidateExtension {
kind_id: 1,
payload: Vec::new(),
}),
annotations: Vec::new(),
display_spans: Vec::new(),
})
.collect())
}
spec
spec: func() -> completion-source-spec
The source's identity (name + doc), the insert_generator pair. Called once at registration.
Example — Declare a completion source's id and doc, completing word queries only · crates/lattice-plugin-host/tests/fixtures/completion-guest/src/lib.rs
fn spec() -> CompletionSourceSpec {
CompletionSourceSpec {
id: "keywords".to_string(),
doc: "Fixture keyword completion source (PH7.6 substrate validation).".to_string(),
// Identifier completion — the default. The phrase-source path is
// covered by org-roam's own tests.
accepts_non_word_query: false,
}
}