Skip to main content

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}