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