picker-source
Direction: guest implements this interface · Capability: none (pure data / dispatch) · Worlds: picker-source-plugin (exports), project-plugin (exports)
Mirrors PickerSourceGenerator (lattice_picker::source). A WASM picker plugin exports this interface to serve the sources it declared through picker-registry; the host wraps the exports as an Arc<dyn PickerSourceGenerator> (PH7.4c.2) and registers it through the SubsystemBoot install seam → PickerRegistry::register_generator, so a plugin source is indistinguishable from a first-party one at the registry. The ⭐ Phase-7-exit interface; exercised by the picker-guest fixture and used by plugins/project.
Uses
raw-candidatefromtypesrouting-payloadfromtypespicker-contextfromtypespicker-accept-outcomefromtypes
Functions (2)
accept
accept: func(source: string, ctx: picker-context, routing: routing-payload) -> result<picker-accept-outcome, string>
Translate the user's chosen routing token into a typed PickerAcceptOutcome the host applies. A mismatch is an err (echoed).
Example — Map the routing token a row carried to the outcome the host performs · crates/lattice-plugin-host/tests/fixtures/picker-guest/src/lib.rs
fn accept(
source: String,
_ctx: PickerContext,
routing: RoutingPayload,
) -> Result<PickerAcceptOutcome, String> {
// OR.5b: the second source's accept is distinguishable too — otherwise a
// test could not tell "routed to the right source" from "there is only
// one body".
if source == SECOND {
return Ok(PickerAcceptOutcome::OpenFile("/second/accepted".to_string()));
}
match routing {
RoutingPayload::OpenFile(p) => Ok(PickerAcceptOutcome::OpenFile(p)),
RoutingPayload::Buffer(id) => Ok(PickerAcceptOutcome::SwitchBuffer(id)),
// OR.5: the create row. The query crosses VERBATIM — the host must
// not have trimmed, lowercased or otherwise had an opinion about a
// namespace it does not own — so the fixture echoes it back inside
// a path the test can compare exactly.
RoutingPayload::Create(query) => {
Ok(PickerAcceptOutcome::OpenFile(format!("/created/{query}")))
}
_ => Err("fixture: unexpected routing token".to_string()),
}
}
init
init: func(source: string, ctx: picker-context, args: list<string>) -> result<list<candidate-pair>, string>
Build the candidate set for :picker <id> <args>. ctx is the owned PickerContext projection (§4.2). Returns the (candidate, routing) pairs; an err string is echoed and the picker stays closed. (One-shot list; the incremental Stream shape — the deferred §15 streaming question — lands with a live source.)
NB: the active buffer's bulk text rides a borrow<document> handle (PH7.3c DocumentResource) that a text-reading source (:picker lines) needs — deferred here (the fuzzy-finder/files exit reads no buffer text, only walks the fs via host-services). Passing a host-owned resource into a guest export has a bindgen-modeling subtlety to resolve; tracked as a focused follow-up (see the slice plan).
source names WHICH of this plugin's registered sources is being built — one component may register several (see picker-registry), and they share one actor and one guest instance.
Example — Build the candidate rows for each picker source this component registered · plugins/project/src/lib.rs
/// `source` is checked rather than assumed: one component may register
/// several sources and they share one actor, so a source id this plugin
/// never registered is untrusted input, not a case to fall through.
fn init(
source: String,
ctx: PickerContext,
_args: Vec<String>,
) -> Result<Vec<CandidatePair>, String> {
let pairs = match source.as_str() {
picker::PROJECTS_PICKER => picker::init(load())?,
// PB.1: the root rides the CONTEXT, not the args — PC.1's rule,
// and the same reason: `:project-buffers` opened from the
// switch-commands menu names a project other than the one the
// buffer is in, and `Effect::OpenPicker { root }` is the seam that
// carries it. Reading `args[0]` would work for this source and
// then be a second convention for the next one.
picker::PROJECT_BUFFERS_PICKER => picker::buffers_init(
&ctx.workspace_root,
ctx.buffers,
ctx.active_buffer.buffer_id,
),
other => return Err(format!("project: no picker source `{other}`")),
};
Ok(pairs
.into_iter()
.map(|(candidate, routing)| CandidatePair { candidate, routing })
.collect())
}
Types (1)
record candidate-pair
record candidate-pair {
candidate: raw-candidate,
routing: routing-payload,
}
One (candidate, routing) pair — the WIT form of the native CandidateBatch element (Vec<(RawCandidate, RoutingPayload)>). The routing token is opaque to the picker; the source emits it here and consumes it in accept.
Fields
candidate:raw-candidaterouting:routing-payload