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