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