lattice_plugin_host/multibuffer_view_host.rs
1//! MV.1 — the multibuffer-view guest world.
2//!
3//! Design: `docs/dev/architecture/plugin-multibuffer-views.md`.
4//!
5//! A view plugin implements `multibuffer-view-plugin`: it exports
6//! `register-multibuffer-views` + `build`, and imports the
7//! `multibuffer-view-registry` seam it declares through. This module holds the
8//! **second `bindgen!`** for that world — the two-bindgen-with-shared-types
9//! trick, with the `with:` map pointing `types` / `host-services` / `logging` /
10//! `project` at the `plugin` world's generated modules so a value crossing here
11//! is the SAME Rust type `WitBoundary` round-trips rather than a fresh,
12//! incompatible copy.
13
14pub(crate) mod bindings {
15 wasmtime::component::bindgen!({
16 world: "multibuffer-view-plugin",
17 path: "../lattice-wit/wit",
18 // `build` is async: producing a view's excerpts may read a store or a
19 // file, and it must never pin the caller's thread.
20 exports: { default: async },
21 with: {
22 "lattice:plugin-host/types": crate::lattice::plugin_host::types,
23 "lattice:plugin-host/host-services": crate::lattice::plugin_host::host_services,
24 "lattice:plugin-host/logging": crate::lattice::plugin_host::logging,
25 "lattice:plugin-host/project": crate::lattice::plugin_host::project,
26 },
27 });
28}
29
30use crate::lattice::plugin_host::types::MultibufferViewSpec;
31
32/// The specs a guest declared during `register-multibuffer-views`.
33///
34/// Recorded on the plugin's `PluginState` and drained by the spawn after the
35/// export returns — the `PickerContributions` shape, for its reason: the guest
36/// calls a host import N times and the host collects, rather than the component
37/// being one view.
38#[derive(Debug, Default)]
39pub struct MultibufferViewContributions {
40 pub specs: Vec<MultibufferViewSpec>,
41}
42
43impl MultibufferViewContributions {
44 /// Record one declared view.
45 ///
46 /// A second registration under an id this same plugin already used
47 /// **replaces** it — that is a reload, not a collision. Two different
48 /// plugins claiming one id is resolved where the provider registry can see
49 /// both (`plugin_view.rs`), because only there is the other claimant known.
50 pub fn declare(&mut self, spec: MultibufferViewSpec) {
51 if let Some(existing) = self.specs.iter_mut().find(|s| s.id == spec.id) {
52 *existing = spec;
53 return;
54 }
55 self.specs.push(spec);
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::lattice::plugin_host::types::MultibufferViewInput;
63
64 fn spec(id: &str, buffer: &str) -> MultibufferViewSpec {
65 MultibufferViewSpec {
66 id: id.to_string(),
67 doc: String::new(),
68 buffer_name: buffer.to_string(),
69 view_mode: None,
70 reuse: true,
71 input: MultibufferViewInput::Pull,
72 }
73 }
74
75 #[test]
76 fn a_guest_may_declare_several_views() {
77 let mut c = MultibufferViewContributions::default();
78 c.declare(spec("a", "*a*"));
79 c.declare(spec("b", "*b*"));
80 assert_eq!(
81 c.specs.len(),
82 2,
83 "the registry shape, not one-per-component"
84 );
85 }
86
87 /// A reload re-declares; the second wins rather than accumulating a
88 /// duplicate the provider registry would then see twice.
89 #[test]
90 fn re_declaring_an_id_replaces_rather_than_appends() {
91 let mut c = MultibufferViewContributions::default();
92 c.declare(spec("a", "*old*"));
93 c.declare(spec("a", "*new*"));
94 assert_eq!(c.specs.len(), 1);
95 assert_eq!(c.specs[0].buffer_name, "*new*");
96 }
97}