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}