extension.rs

 1pub mod extension_builder;
 2mod extension_manifest;
 3
 4use std::path::Path;
 5use std::sync::Arc;
 6
 7use anyhow::{anyhow, bail, Context as _, Result};
 8use async_trait::async_trait;
 9use gpui::Task;
10use semantic_version::SemanticVersion;
11
12pub use crate::extension_manifest::*;
13
14pub trait KeyValueStoreDelegate: Send + Sync + 'static {
15    fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
16}
17
18#[async_trait]
19pub trait Extension: Send + Sync + 'static {
20    /// Returns the [`ExtensionManifest`] for this extension.
21    fn manifest(&self) -> Arc<ExtensionManifest>;
22
23    /// Returns the path to this extension's working directory.
24    fn work_dir(&self) -> Arc<Path>;
25
26    async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
27
28    async fn index_docs(
29        &self,
30        provider: Arc<str>,
31        package_name: Arc<str>,
32        kv_store: Arc<dyn KeyValueStoreDelegate>,
33    ) -> Result<()>;
34}
35
36pub fn parse_wasm_extension_version(
37    extension_id: &str,
38    wasm_bytes: &[u8],
39) -> Result<SemanticVersion> {
40    let mut version = None;
41
42    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
43        if let wasmparser::Payload::CustomSection(s) =
44            part.context("error parsing wasm extension")?
45        {
46            if s.name() == "zed:api-version" {
47                version = parse_wasm_extension_version_custom_section(s.data());
48                if version.is_none() {
49                    bail!(
50                        "extension {} has invalid zed:api-version section: {:?}",
51                        extension_id,
52                        s.data()
53                    );
54                }
55            }
56        }
57    }
58
59    // The reason we wait until we're done parsing all of the Wasm bytes to return the version
60    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
61    //
62    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
63    // earlier as an `Err` rather than as a panic.
64    version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
65}
66
67fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
68    if data.len() == 6 {
69        Some(SemanticVersion::new(
70            u16::from_be_bytes([data[0], data[1]]) as _,
71            u16::from_be_bytes([data[2], data[3]]) as _,
72            u16::from_be_bytes([data[4], data[5]]) as _,
73        ))
74    } else {
75        None
76    }
77}