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