extension_api.rs

  1//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
  2
  3pub mod http_client;
  4pub mod process;
  5pub mod settings;
  6
  7use core::fmt;
  8
  9use wit::*;
 10
 11pub use serde_json;
 12
 13// WIT re-exports.
 14//
 15// We explicitly enumerate the symbols we want to re-export, as there are some
 16// that we may want to shadow to provide a cleaner Rust API.
 17pub use wit::{
 18    CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
 19    KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
 20    make_file_executable,
 21    zed::extension::context_server::ContextServerConfiguration,
 22    zed::extension::dap::{
 23        DebugAdapterBinary, DebugRequest, DebugTaskDefinition, StartDebuggingRequestArguments,
 24        StartDebuggingRequestArgumentsRequest, TcpArguments, TcpArgumentsTemplate,
 25    },
 26    zed::extension::github::{
 27        GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
 28        latest_github_release,
 29    },
 30    zed::extension::nodejs::{
 31        node_binary_path, npm_install_package, npm_package_installed_version,
 32        npm_package_latest_version,
 33    },
 34    zed::extension::platform::{Architecture, Os, current_platform},
 35    zed::extension::slash_command::{
 36        SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
 37    },
 38};
 39
 40// Undocumented WIT re-exports.
 41//
 42// These are symbols that need to be public for the purposes of implementing
 43// the extension host, but aren't relevant to extension authors.
 44#[doc(hidden)]
 45pub use wit::Guest;
 46
 47/// Constructs for interacting with language servers over the
 48/// Language Server Protocol (LSP).
 49pub mod lsp {
 50    pub use crate::wit::zed::extension::lsp::{
 51        Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
 52    };
 53}
 54
 55/// A result returned from a Zed extension.
 56pub type Result<T, E = String> = core::result::Result<T, E>;
 57
 58/// Updates the installation status for the given language server.
 59pub fn set_language_server_installation_status(
 60    language_server_id: &LanguageServerId,
 61    status: &LanguageServerInstallationStatus,
 62) {
 63    wit::set_language_server_installation_status(&language_server_id.0, status)
 64}
 65
 66/// A Zed extension.
 67pub trait Extension: Send + Sync {
 68    /// Returns a new instance of the extension.
 69    fn new() -> Self
 70    where
 71        Self: Sized;
 72
 73    /// Returns the command used to start the language server for the specified
 74    /// language.
 75    fn language_server_command(
 76        &mut self,
 77        _language_server_id: &LanguageServerId,
 78        _worktree: &Worktree,
 79    ) -> Result<Command> {
 80        Err("`language_server_command` not implemented".to_string())
 81    }
 82
 83    /// Returns the initialization options to pass to the specified language server.
 84    fn language_server_initialization_options(
 85        &mut self,
 86        _language_server_id: &LanguageServerId,
 87        _worktree: &Worktree,
 88    ) -> Result<Option<serde_json::Value>> {
 89        Ok(None)
 90    }
 91
 92    /// Returns the workspace configuration options to pass to the language server.
 93    fn language_server_workspace_configuration(
 94        &mut self,
 95        _language_server_id: &LanguageServerId,
 96        _worktree: &Worktree,
 97    ) -> Result<Option<serde_json::Value>> {
 98        Ok(None)
 99    }
100
101    /// Returns the initialization options to pass to the other language server.
102    fn language_server_additional_initialization_options(
103        &mut self,
104        _language_server_id: &LanguageServerId,
105        _target_language_server_id: &LanguageServerId,
106        _worktree: &Worktree,
107    ) -> Result<Option<serde_json::Value>> {
108        Ok(None)
109    }
110
111    /// Returns the workspace configuration options to pass to the other language server.
112    fn language_server_additional_workspace_configuration(
113        &mut self,
114        _language_server_id: &LanguageServerId,
115        _target_language_server_id: &LanguageServerId,
116        _worktree: &Worktree,
117    ) -> Result<Option<serde_json::Value>> {
118        Ok(None)
119    }
120
121    /// Returns the label for the given completion.
122    fn label_for_completion(
123        &self,
124        _language_server_id: &LanguageServerId,
125        _completion: Completion,
126    ) -> Option<CodeLabel> {
127        None
128    }
129
130    /// Returns the label for the given symbol.
131    fn label_for_symbol(
132        &self,
133        _language_server_id: &LanguageServerId,
134        _symbol: Symbol,
135    ) -> Option<CodeLabel> {
136        None
137    }
138
139    /// Returns the completions that should be shown when completing the provided slash command with the given query.
140    fn complete_slash_command_argument(
141        &self,
142        _command: SlashCommand,
143        _args: Vec<String>,
144    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
145        Ok(Vec::new())
146    }
147
148    /// Returns the output from running the provided slash command.
149    fn run_slash_command(
150        &self,
151        _command: SlashCommand,
152        _args: Vec<String>,
153        _worktree: Option<&Worktree>,
154    ) -> Result<SlashCommandOutput, String> {
155        Err("`run_slash_command` not implemented".to_string())
156    }
157
158    /// Returns the command used to start a context server.
159    fn context_server_command(
160        &mut self,
161        _context_server_id: &ContextServerId,
162        _project: &Project,
163    ) -> Result<Command> {
164        Err("`context_server_command` not implemented".to_string())
165    }
166
167    /// Returns the configuration options for the specified context server.
168    fn context_server_configuration(
169        &mut self,
170        _context_server_id: &ContextServerId,
171        _project: &Project,
172    ) -> Result<Option<ContextServerConfiguration>> {
173        Ok(None)
174    }
175
176    /// Returns a list of package names as suggestions to be included in the
177    /// search results of the `/docs` slash command.
178    ///
179    /// This can be used to provide completions for known packages (e.g., from the
180    /// local project or a registry) before a package has been indexed.
181    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
182        Ok(Vec::new())
183    }
184
185    /// Indexes the docs for the specified package.
186    fn index_docs(
187        &self,
188        _provider: String,
189        _package: String,
190        _database: &KeyValueStore,
191    ) -> Result<(), String> {
192        Err("`index_docs` not implemented".to_string())
193    }
194
195    /// Returns the debug adapter binary for the specified adapter name and configuration.
196    fn get_dap_binary(
197        &mut self,
198        _adapter_name: String,
199        _config: DebugTaskDefinition,
200        _user_provided_path: Option<String>,
201        _worktree: &Worktree,
202    ) -> Result<DebugAdapterBinary, String> {
203        Err("`get_dap_binary` not implemented".to_string())
204    }
205}
206
207/// Registers the provided type as a Zed extension.
208///
209/// The type must implement the [`Extension`] trait.
210#[macro_export]
211macro_rules! register_extension {
212    ($extension_type:ty) => {
213        #[unsafe(export_name = "init-extension")]
214        pub extern "C" fn __init_extension() {
215            std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
216            zed_extension_api::register_extension(|| {
217                Box::new(<$extension_type as zed_extension_api::Extension>::new())
218            });
219        }
220    };
221}
222
223#[doc(hidden)]
224pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
225    unsafe { EXTENSION = Some((build_extension)()) }
226}
227
228fn extension() -> &'static mut dyn Extension {
229    #[expect(static_mut_refs)]
230    unsafe {
231        EXTENSION.as_deref_mut().unwrap()
232    }
233}
234
235static mut EXTENSION: Option<Box<dyn Extension>> = None;
236
237#[cfg(target_arch = "wasm32")]
238#[unsafe(link_section = "zed:api-version")]
239#[doc(hidden)]
240pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
241
242mod wit {
243
244    wit_bindgen::generate!({
245        skip: ["init-extension"],
246        path: "./wit/since_v0.6.0",
247    });
248}
249
250wit::export!(Component);
251
252struct Component;
253
254impl wit::Guest for Component {
255    fn language_server_command(
256        language_server_id: String,
257        worktree: &wit::Worktree,
258    ) -> Result<wit::Command> {
259        let language_server_id = LanguageServerId(language_server_id);
260        extension().language_server_command(&language_server_id, worktree)
261    }
262
263    fn language_server_initialization_options(
264        language_server_id: String,
265        worktree: &Worktree,
266    ) -> Result<Option<String>, String> {
267        let language_server_id = LanguageServerId(language_server_id);
268        Ok(extension()
269            .language_server_initialization_options(&language_server_id, worktree)?
270            .and_then(|value| serde_json::to_string(&value).ok()))
271    }
272
273    fn language_server_workspace_configuration(
274        language_server_id: String,
275        worktree: &Worktree,
276    ) -> Result<Option<String>, String> {
277        let language_server_id = LanguageServerId(language_server_id);
278        Ok(extension()
279            .language_server_workspace_configuration(&language_server_id, worktree)?
280            .and_then(|value| serde_json::to_string(&value).ok()))
281    }
282
283    fn language_server_additional_initialization_options(
284        language_server_id: String,
285        target_language_server_id: String,
286        worktree: &Worktree,
287    ) -> Result<Option<String>, String> {
288        let language_server_id = LanguageServerId(language_server_id);
289        let target_language_server_id = LanguageServerId(target_language_server_id);
290        Ok(extension()
291            .language_server_additional_initialization_options(
292                &language_server_id,
293                &target_language_server_id,
294                worktree,
295            )?
296            .and_then(|value| serde_json::to_string(&value).ok()))
297    }
298
299    fn language_server_additional_workspace_configuration(
300        language_server_id: String,
301        target_language_server_id: String,
302        worktree: &Worktree,
303    ) -> Result<Option<String>, String> {
304        let language_server_id = LanguageServerId(language_server_id);
305        let target_language_server_id = LanguageServerId(target_language_server_id);
306        Ok(extension()
307            .language_server_additional_workspace_configuration(
308                &language_server_id,
309                &target_language_server_id,
310                worktree,
311            )?
312            .and_then(|value| serde_json::to_string(&value).ok()))
313    }
314
315    fn labels_for_completions(
316        language_server_id: String,
317        completions: Vec<Completion>,
318    ) -> Result<Vec<Option<CodeLabel>>, String> {
319        let language_server_id = LanguageServerId(language_server_id);
320        let mut labels = Vec::new();
321        for (ix, completion) in completions.into_iter().enumerate() {
322            let label = extension().label_for_completion(&language_server_id, completion);
323            if let Some(label) = label {
324                labels.resize(ix + 1, None);
325                *labels.last_mut().unwrap() = Some(label);
326            }
327        }
328        Ok(labels)
329    }
330
331    fn labels_for_symbols(
332        language_server_id: String,
333        symbols: Vec<Symbol>,
334    ) -> Result<Vec<Option<CodeLabel>>, String> {
335        let language_server_id = LanguageServerId(language_server_id);
336        let mut labels = Vec::new();
337        for (ix, symbol) in symbols.into_iter().enumerate() {
338            let label = extension().label_for_symbol(&language_server_id, symbol);
339            if let Some(label) = label {
340                labels.resize(ix + 1, None);
341                *labels.last_mut().unwrap() = Some(label);
342            }
343        }
344        Ok(labels)
345    }
346
347    fn complete_slash_command_argument(
348        command: SlashCommand,
349        args: Vec<String>,
350    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
351        extension().complete_slash_command_argument(command, args)
352    }
353
354    fn run_slash_command(
355        command: SlashCommand,
356        args: Vec<String>,
357        worktree: Option<&Worktree>,
358    ) -> Result<SlashCommandOutput, String> {
359        extension().run_slash_command(command, args, worktree)
360    }
361
362    fn context_server_command(
363        context_server_id: String,
364        project: &Project,
365    ) -> Result<wit::Command> {
366        let context_server_id = ContextServerId(context_server_id);
367        extension().context_server_command(&context_server_id, project)
368    }
369
370    fn context_server_configuration(
371        context_server_id: String,
372        project: &Project,
373    ) -> Result<Option<ContextServerConfiguration>, String> {
374        let context_server_id = ContextServerId(context_server_id);
375        extension().context_server_configuration(&context_server_id, project)
376    }
377
378    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
379        extension().suggest_docs_packages(provider)
380    }
381
382    fn index_docs(
383        provider: String,
384        package: String,
385        database: &KeyValueStore,
386    ) -> Result<(), String> {
387        extension().index_docs(provider, package, database)
388    }
389
390    fn get_dap_binary(
391        adapter_name: String,
392        config: DebugTaskDefinition,
393        user_installed_path: Option<String>,
394        worktree: &Worktree,
395    ) -> Result<wit::DebugAdapterBinary, String> {
396        extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
397    }
398}
399
400/// The ID of a language server.
401#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
402pub struct LanguageServerId(String);
403
404impl AsRef<str> for LanguageServerId {
405    fn as_ref(&self) -> &str {
406        &self.0
407    }
408}
409
410impl fmt::Display for LanguageServerId {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        write!(f, "{}", self.0)
413    }
414}
415
416/// The ID of a context server.
417#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
418pub struct ContextServerId(String);
419
420impl AsRef<str> for ContextServerId {
421    fn as_ref(&self) -> &str {
422        &self.0
423    }
424}
425
426impl fmt::Display for ContextServerId {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        write!(f, "{}", self.0)
429    }
430}
431
432impl CodeLabelSpan {
433    /// Returns a [`CodeLabelSpan::CodeRange`].
434    pub fn code_range(range: impl Into<wit::Range>) -> Self {
435        Self::CodeRange(range.into())
436    }
437
438    /// Returns a [`CodeLabelSpan::Literal`].
439    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
440        Self::Literal(CodeLabelSpanLiteral {
441            text: text.into(),
442            highlight_name,
443        })
444    }
445}
446
447impl From<std::ops::Range<u32>> for wit::Range {
448    fn from(value: std::ops::Range<u32>) -> Self {
449        Self {
450            start: value.start,
451            end: value.end,
452        }
453    }
454}
455
456impl From<std::ops::Range<usize>> for wit::Range {
457    fn from(value: std::ops::Range<usize>) -> Self {
458        Self {
459            start: value.start as u32,
460            end: value.end as u32,
461        }
462    }
463}