1mod capabilities;
2pub mod extension_builder;
3mod extension_events;
4mod extension_host_proxy;
5mod extension_manifest;
6mod types;
7
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11use ::lsp::LanguageServerName;
12use anyhow::{Context as _, Result, bail};
13use async_trait::async_trait;
14use fs::normalize_path;
15use gpui::{App, Task};
16use language::LanguageName;
17use semver::Version;
18use task::{SpawnInTerminal, ZedDebugConfig};
19use util::rel_path::RelPath;
20
21pub use crate::capabilities::*;
22pub use crate::extension_events::*;
23pub use crate::extension_host_proxy::*;
24pub use crate::extension_manifest::*;
25pub use crate::types::*;
26
27/// Initializes the `extension` crate.
28pub fn init(cx: &mut App) {
29 extension_events::init(cx);
30 ExtensionHostProxy::default_global(cx);
31}
32
33#[async_trait]
34pub trait WorktreeDelegate: Send + Sync + 'static {
35 fn id(&self) -> u64;
36 fn root_path(&self) -> String;
37 async fn read_text_file(&self, path: &RelPath) -> Result<String>;
38 async fn which(&self, binary_name: String) -> Option<String>;
39 async fn shell_env(&self) -> Vec<(String, String)>;
40}
41
42pub trait ProjectDelegate: Send + Sync + 'static {
43 fn worktree_ids(&self) -> Vec<u64>;
44}
45
46pub trait KeyValueStoreDelegate: Send + Sync + 'static {
47 fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
48}
49
50#[async_trait]
51pub trait Extension: Send + Sync + 'static {
52 /// Returns the [`ExtensionManifest`] for this extension.
53 fn manifest(&self) -> Arc<ExtensionManifest>;
54
55 /// Returns the path to this extension's working directory.
56 fn work_dir(&self) -> Arc<Path>;
57
58 /// Returns a path relative to this extension's working directory.
59 fn path_from_extension(&self, path: &Path) -> PathBuf {
60 normalize_path(&self.work_dir().join(path))
61 }
62
63 async fn language_server_command(
64 &self,
65 language_server_id: LanguageServerName,
66 language_name: LanguageName,
67 worktree: Arc<dyn WorktreeDelegate>,
68 ) -> Result<Command>;
69
70 async fn language_server_initialization_options(
71 &self,
72 language_server_id: LanguageServerName,
73 language_name: LanguageName,
74 worktree: Arc<dyn WorktreeDelegate>,
75 ) -> Result<Option<String>>;
76
77 async fn language_server_workspace_configuration(
78 &self,
79 language_server_id: LanguageServerName,
80 worktree: Arc<dyn WorktreeDelegate>,
81 ) -> Result<Option<String>>;
82
83 async fn language_server_initialization_options_schema(
84 &self,
85 language_server_id: LanguageServerName,
86 worktree: Arc<dyn WorktreeDelegate>,
87 ) -> Result<Option<String>>;
88
89 async fn language_server_workspace_configuration_schema(
90 &self,
91 language_server_id: LanguageServerName,
92 worktree: Arc<dyn WorktreeDelegate>,
93 ) -> Result<Option<String>>;
94
95 async fn language_server_additional_initialization_options(
96 &self,
97 language_server_id: LanguageServerName,
98 target_language_server_id: LanguageServerName,
99 worktree: Arc<dyn WorktreeDelegate>,
100 ) -> Result<Option<String>>;
101
102 async fn language_server_additional_workspace_configuration(
103 &self,
104 language_server_id: LanguageServerName,
105 target_language_server_id: LanguageServerName,
106 worktree: Arc<dyn WorktreeDelegate>,
107 ) -> Result<Option<String>>;
108
109 async fn labels_for_completions(
110 &self,
111 language_server_id: LanguageServerName,
112 completions: Vec<Completion>,
113 ) -> Result<Vec<Option<CodeLabel>>>;
114
115 async fn labels_for_symbols(
116 &self,
117 language_server_id: LanguageServerName,
118 symbols: Vec<Symbol>,
119 ) -> Result<Vec<Option<CodeLabel>>>;
120
121 async fn complete_slash_command_argument(
122 &self,
123 command: SlashCommand,
124 arguments: Vec<String>,
125 ) -> Result<Vec<SlashCommandArgumentCompletion>>;
126
127 async fn run_slash_command(
128 &self,
129 command: SlashCommand,
130 arguments: Vec<String>,
131 worktree: Option<Arc<dyn WorktreeDelegate>>,
132 ) -> Result<SlashCommandOutput>;
133
134 async fn context_server_command(
135 &self,
136 context_server_id: Arc<str>,
137 project: Arc<dyn ProjectDelegate>,
138 ) -> Result<Command>;
139
140 async fn context_server_configuration(
141 &self,
142 context_server_id: Arc<str>,
143 project: Arc<dyn ProjectDelegate>,
144 ) -> Result<Option<ContextServerConfiguration>>;
145
146 async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
147
148 async fn index_docs(
149 &self,
150 provider: Arc<str>,
151 package_name: Arc<str>,
152 kv_store: Arc<dyn KeyValueStoreDelegate>,
153 ) -> Result<()>;
154
155 async fn get_dap_binary(
156 &self,
157 dap_name: Arc<str>,
158 config: DebugTaskDefinition,
159 user_installed_path: Option<PathBuf>,
160 worktree: Arc<dyn WorktreeDelegate>,
161 ) -> Result<DebugAdapterBinary>;
162
163 async fn dap_request_kind(
164 &self,
165 dap_name: Arc<str>,
166 config: serde_json::Value,
167 ) -> Result<StartDebuggingRequestArgumentsRequest>;
168
169 async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario>;
170
171 async fn dap_locator_create_scenario(
172 &self,
173 locator_name: String,
174 build_config_template: BuildTaskTemplate,
175 resolved_label: String,
176 debug_adapter_name: String,
177 ) -> Result<Option<DebugScenario>>;
178 async fn run_dap_locator(
179 &self,
180 locator_name: String,
181 config: SpawnInTerminal,
182 ) -> Result<DebugRequest>;
183}
184
185pub fn parse_wasm_extension_version(extension_id: &str, wasm_bytes: &[u8]) -> Result<Version> {
186 let mut version = None;
187
188 for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
189 if let wasmparser::Payload::CustomSection(s) =
190 part.context("error parsing wasm extension")?
191 && s.name() == "zed:api-version"
192 {
193 version = parse_wasm_extension_version_custom_section(s.data());
194 if version.is_none() {
195 bail!(
196 "extension {} has invalid zed:api-version section: {:?}",
197 extension_id,
198 s.data()
199 );
200 }
201 }
202 }
203
204 // The reason we wait until we're done parsing all of the Wasm bytes to return the version
205 // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
206 //
207 // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
208 // earlier as an `Err` rather than as a panic.
209 version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
210}
211
212fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<Version> {
213 if data.len() == 6 {
214 Some(Version::new(
215 u16::from_be_bytes([data[0], data[1]]) as _,
216 u16::from_be_bytes([data[2], data[3]]) as _,
217 u16::from_be_bytes([data[4], data[5]]) as _,
218 ))
219 } else {
220 None
221 }
222}