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