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 semantic_version::SemanticVersion;
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_additional_initialization_options(
84 &self,
85 language_server_id: LanguageServerName,
86 target_language_server_id: LanguageServerName,
87 worktree: Arc<dyn WorktreeDelegate>,
88 ) -> Result<Option<String>>;
89
90 async fn language_server_additional_workspace_configuration(
91 &self,
92 language_server_id: LanguageServerName,
93 target_language_server_id: LanguageServerName,
94 worktree: Arc<dyn WorktreeDelegate>,
95 ) -> Result<Option<String>>;
96
97 async fn labels_for_completions(
98 &self,
99 language_server_id: LanguageServerName,
100 completions: Vec<Completion>,
101 ) -> Result<Vec<Option<CodeLabel>>>;
102
103 async fn labels_for_symbols(
104 &self,
105 language_server_id: LanguageServerName,
106 symbols: Vec<Symbol>,
107 ) -> Result<Vec<Option<CodeLabel>>>;
108
109 async fn complete_slash_command_argument(
110 &self,
111 command: SlashCommand,
112 arguments: Vec<String>,
113 ) -> Result<Vec<SlashCommandArgumentCompletion>>;
114
115 async fn run_slash_command(
116 &self,
117 command: SlashCommand,
118 arguments: Vec<String>,
119 worktree: Option<Arc<dyn WorktreeDelegate>>,
120 ) -> Result<SlashCommandOutput>;
121
122 async fn context_server_command(
123 &self,
124 context_server_id: Arc<str>,
125 project: Arc<dyn ProjectDelegate>,
126 ) -> Result<Command>;
127
128 async fn context_server_configuration(
129 &self,
130 context_server_id: Arc<str>,
131 project: Arc<dyn ProjectDelegate>,
132 ) -> Result<Option<ContextServerConfiguration>>;
133
134 async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
135
136 async fn index_docs(
137 &self,
138 provider: Arc<str>,
139 package_name: Arc<str>,
140 kv_store: Arc<dyn KeyValueStoreDelegate>,
141 ) -> Result<()>;
142
143 async fn get_dap_binary(
144 &self,
145 dap_name: Arc<str>,
146 config: DebugTaskDefinition,
147 user_installed_path: Option<PathBuf>,
148 worktree: Arc<dyn WorktreeDelegate>,
149 ) -> Result<DebugAdapterBinary>;
150
151 async fn dap_request_kind(
152 &self,
153 dap_name: Arc<str>,
154 config: serde_json::Value,
155 ) -> Result<StartDebuggingRequestArgumentsRequest>;
156
157 async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario>;
158
159 async fn dap_locator_create_scenario(
160 &self,
161 locator_name: String,
162 build_config_template: BuildTaskTemplate,
163 resolved_label: String,
164 debug_adapter_name: String,
165 ) -> Result<Option<DebugScenario>>;
166 async fn run_dap_locator(
167 &self,
168 locator_name: String,
169 config: SpawnInTerminal,
170 ) -> Result<DebugRequest>;
171}
172
173pub fn parse_wasm_extension_version(
174 extension_id: &str,
175 wasm_bytes: &[u8],
176) -> Result<SemanticVersion> {
177 let mut version = None;
178
179 for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
180 if let wasmparser::Payload::CustomSection(s) =
181 part.context("error parsing wasm extension")?
182 && s.name() == "zed:api-version"
183 {
184 version = parse_wasm_extension_version_custom_section(s.data());
185 if version.is_none() {
186 bail!(
187 "extension {} has invalid zed:api-version section: {:?}",
188 extension_id,
189 s.data()
190 );
191 }
192 }
193 }
194
195 // The reason we wait until we're done parsing all of the Wasm bytes to return the version
196 // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
197 //
198 // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
199 // earlier as an `Err` rather than as a panic.
200 version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
201}
202
203fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
204 if data.len() == 6 {
205 Some(SemanticVersion::new(
206 u16::from_be_bytes([data[0], data[1]]) as _,
207 u16::from_be_bytes([data[2], data[3]]) as _,
208 u16::from_be_bytes([data[4], data[5]]) as _,
209 ))
210 } else {
211 None
212 }
213}