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        AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, BuildTaskTemplate,
 24        DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition,
 25        LaunchRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
 26        TaskTemplate, TcpArguments, TcpArgumentsTemplate, resolve_tcp_template,
 27    },
 28    zed::extension::github::{
 29        GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
 30        latest_github_release,
 31    },
 32    zed::extension::nodejs::{
 33        node_binary_path, npm_install_package, npm_package_installed_version,
 34        npm_package_latest_version,
 35    },
 36    zed::extension::platform::{Architecture, Os, current_platform},
 37    zed::extension::slash_command::{
 38        SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
 39    },
 40};
 41
 42// Undocumented WIT re-exports.
 43//
 44// These are symbols that need to be public for the purposes of implementing
 45// the extension host, but aren't relevant to extension authors.
 46#[doc(hidden)]
 47pub use wit::Guest;
 48
 49/// Constructs for interacting with language servers over the
 50/// Language Server Protocol (LSP).
 51pub mod lsp {
 52    pub use crate::wit::zed::extension::lsp::{
 53        Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
 54    };
 55}
 56
 57/// A result returned from a Zed extension.
 58pub type Result<T, E = String> = core::result::Result<T, E>;
 59
 60/// Updates the installation status for the given language server.
 61pub fn set_language_server_installation_status(
 62    language_server_id: &LanguageServerId,
 63    status: &LanguageServerInstallationStatus,
 64) {
 65    wit::set_language_server_installation_status(&language_server_id.0, status)
 66}
 67
 68/// A Zed extension.
 69pub trait Extension: Send + Sync {
 70    /// Returns a new instance of the extension.
 71    fn new() -> Self
 72    where
 73        Self: Sized;
 74
 75    /// Returns the command used to start the language server for the specified
 76    /// language.
 77    fn language_server_command(
 78        &mut self,
 79        _language_server_id: &LanguageServerId,
 80        _worktree: &Worktree,
 81    ) -> Result<Command> {
 82        Err("`language_server_command` not implemented".to_string())
 83    }
 84
 85    /// Returns the initialization options to pass to the specified language server.
 86    fn language_server_initialization_options(
 87        &mut self,
 88        _language_server_id: &LanguageServerId,
 89        _worktree: &Worktree,
 90    ) -> Result<Option<serde_json::Value>> {
 91        Ok(None)
 92    }
 93
 94    /// Returns the workspace configuration options to pass to the language server.
 95    fn language_server_workspace_configuration(
 96        &mut self,
 97        _language_server_id: &LanguageServerId,
 98        _worktree: &Worktree,
 99    ) -> Result<Option<serde_json::Value>> {
100        Ok(None)
101    }
102
103    /// Returns the JSON schema for the initialization options.
104    ///
105    /// The schema must conform to the JSON Schema speification.
106    fn language_server_initialization_options_schema(
107        &mut self,
108        _language_server_id: &LanguageServerId,
109        _worktree: &Worktree,
110    ) -> Option<serde_json::Value> {
111        None
112    }
113
114    /// Returns the JSON schema for the workspace configuration.
115    ///
116    /// The schema must conform to the JSON Schema specification.
117    fn language_server_workspace_configuration_schema(
118        &mut self,
119        _language_server_id: &LanguageServerId,
120        _worktree: &Worktree,
121    ) -> Option<serde_json::Value> {
122        None
123    }
124
125    /// Returns the initialization options to pass to the other language server.
126    fn language_server_additional_initialization_options(
127        &mut self,
128        _language_server_id: &LanguageServerId,
129        _target_language_server_id: &LanguageServerId,
130        _worktree: &Worktree,
131    ) -> Result<Option<serde_json::Value>> {
132        Ok(None)
133    }
134
135    /// Returns the workspace configuration options to pass to the other language server.
136    fn language_server_additional_workspace_configuration(
137        &mut self,
138        _language_server_id: &LanguageServerId,
139        _target_language_server_id: &LanguageServerId,
140        _worktree: &Worktree,
141    ) -> Result<Option<serde_json::Value>> {
142        Ok(None)
143    }
144
145    /// Returns the label for the given completion.
146    fn label_for_completion(
147        &self,
148        _language_server_id: &LanguageServerId,
149        _completion: Completion,
150    ) -> Option<CodeLabel> {
151        None
152    }
153
154    /// Returns the label for the given symbol.
155    fn label_for_symbol(
156        &self,
157        _language_server_id: &LanguageServerId,
158        _symbol: Symbol,
159    ) -> Option<CodeLabel> {
160        None
161    }
162
163    /// Returns the completions that should be shown when completing the provided slash command with the given query.
164    fn complete_slash_command_argument(
165        &self,
166        _command: SlashCommand,
167        _args: Vec<String>,
168    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
169        Ok(Vec::new())
170    }
171
172    /// Returns the output from running the provided slash command.
173    fn run_slash_command(
174        &self,
175        _command: SlashCommand,
176        _args: Vec<String>,
177        _worktree: Option<&Worktree>,
178    ) -> Result<SlashCommandOutput, String> {
179        Err("`run_slash_command` not implemented".to_string())
180    }
181
182    /// Returns the command used to start a context server.
183    fn context_server_command(
184        &mut self,
185        _context_server_id: &ContextServerId,
186        _project: &Project,
187    ) -> Result<Command> {
188        Err("`context_server_command` not implemented".to_string())
189    }
190
191    /// Returns the configuration options for the specified context server.
192    fn context_server_configuration(
193        &mut self,
194        _context_server_id: &ContextServerId,
195        _project: &Project,
196    ) -> Result<Option<ContextServerConfiguration>> {
197        Ok(None)
198    }
199
200    /// Returns a list of package names as suggestions to be included in the
201    /// search results of the `/docs` slash command.
202    ///
203    /// This can be used to provide completions for known packages (e.g., from the
204    /// local project or a registry) before a package has been indexed.
205    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
206        Ok(Vec::new())
207    }
208
209    /// Indexes the docs for the specified package.
210    fn index_docs(
211        &self,
212        _provider: String,
213        _package: String,
214        _database: &KeyValueStore,
215    ) -> Result<(), String> {
216        Err("`index_docs` not implemented".to_string())
217    }
218
219    /// Returns the debug adapter binary for the specified adapter name and configuration.
220    fn get_dap_binary(
221        &mut self,
222        _adapter_name: String,
223        _config: DebugTaskDefinition,
224        _user_provided_debug_adapter_path: Option<String>,
225        _worktree: &Worktree,
226    ) -> Result<DebugAdapterBinary, String> {
227        Err("`get_dap_binary` not implemented".to_string())
228    }
229
230    /// Determines whether the specified adapter configuration should *launch* a new debuggee process
231    /// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
232    /// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
233    fn dap_request_kind(
234        &mut self,
235        _adapter_name: String,
236        _config: serde_json::Value,
237    ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
238        Err("`dap_request_kind` not implemented".to_string())
239    }
240    /// Converts a high-level definition of a debug scenario (originating in a new session UI) to a "low-level" configuration suitable for a particular adapter.
241    ///
242    /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
243    /// create a configuration that can be used to start a debug session.
244    fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
245        Err("`dap_config_to_scenario` not implemented".to_string())
246    }
247
248    /// Locators are entities that convert a Zed task into a debug scenario.
249    ///
250    /// They can be provided even by extensions that don't provide a debug adapter.
251    /// For all tasks applicable to a given buffer, Zed will query all locators to find one that can turn the task into a debug scenario.
252    /// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
253    /// be resolved with [`Extension::run_dap_locator`].
254    ///
255    /// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
256    /// 1. We may need to modify the task; in this case, it is problematic that `cargo run` spawns a binary. We should turn `cargo run` into a debug scenario with
257    ///    `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
258    /// 2. Then, after the build task finishes, we will run `run_dap_locator` of the locator that produced the build task to find the program to be debugged. This function
259    ///    should give us a debugger-agnostic configuration for launching a debug target (that we end up resolving with [`Extension::dap_config_to_scenario`]). It's almost as if the user
260    ///    found the artifact path by themselves.
261    ///
262    /// Note that you're not obliged to use build tasks with locators. Specifically, it is sufficient to provide a debug configuration directly in the return value of
263    /// `dap_locator_create_scenario` if you're able to do that. Make sure to not fill out `build` field in that case, as that will prevent Zed from running second phase of resolution in such case.
264    /// This might be of particular relevance to interpreted languages.
265    fn dap_locator_create_scenario(
266        &mut self,
267        _locator_name: String,
268        _build_task: TaskTemplate,
269        _resolved_label: String,
270        _debug_adapter_name: String,
271    ) -> Option<DebugScenario> {
272        None
273    }
274
275    /// Runs the second phase of locator resolution.
276    /// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
277    fn run_dap_locator(
278        &mut self,
279        _locator_name: String,
280        _build_task: TaskTemplate,
281    ) -> Result<DebugRequest, String> {
282        Err("`run_dap_locator` not implemented".to_string())
283    }
284}
285
286/// Registers the provided type as a Zed extension.
287///
288/// The type must implement the [`Extension`] trait.
289#[macro_export]
290macro_rules! register_extension {
291    ($extension_type:ty) => {
292        #[cfg(target_os = "wasi")]
293        mod wasi_ext {
294            unsafe extern "C" {
295                static mut errno: i32;
296                pub static mut __wasilibc_cwd: *mut std::ffi::c_char;
297            }
298
299            pub fn init_cwd() {
300                unsafe {
301                    // Ensure that our chdir function is linked, instead of the
302                    // one from wasi-libc in the chdir.o translation unit. Otherwise
303                    // we risk linking in `__wasilibc_find_relpath_alloc` which
304                    // is a weak symbol and is being used by
305                    // `__wasilibc_find_relpath`, which we do not want on
306                    // Windows.
307                    chdir(std::ptr::null());
308
309                    __wasilibc_cwd = std::ffi::CString::new(std::env::var("PWD").unwrap())
310                        .unwrap()
311                        .into_raw()
312                        .cast();
313                }
314            }
315
316            #[unsafe(no_mangle)]
317            pub unsafe extern "C" fn chdir(raw_path: *const std::ffi::c_char) -> i32 {
318                // Forbid extensions from changing CWD and so return an appropriate error code.
319                errno = 58; // NOTSUP
320                return -1;
321            }
322        }
323
324        #[unsafe(export_name = "init-extension")]
325        pub extern "C" fn __init_extension() {
326            #[cfg(target_os = "wasi")]
327            wasi_ext::init_cwd();
328
329            zed_extension_api::register_extension(|| {
330                Box::new(<$extension_type as zed_extension_api::Extension>::new())
331            });
332        }
333    };
334}
335
336#[doc(hidden)]
337pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
338    unsafe { EXTENSION = Some((build_extension)()) }
339}
340
341fn extension() -> &'static mut dyn Extension {
342    #[expect(static_mut_refs)]
343    unsafe {
344        EXTENSION.as_deref_mut().unwrap()
345    }
346}
347
348static mut EXTENSION: Option<Box<dyn Extension>> = None;
349
350#[cfg(target_arch = "wasm32")]
351#[unsafe(link_section = "zed:api-version")]
352#[doc(hidden)]
353pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
354
355mod wit {
356    wit_bindgen::generate!({
357        skip: ["init-extension"],
358        path: "./wit/since_v0.8.0",
359    });
360}
361
362wit::export!(Component);
363
364struct Component;
365
366impl wit::Guest for Component {
367    fn language_server_command(
368        language_server_id: String,
369        worktree: &wit::Worktree,
370    ) -> Result<wit::Command> {
371        let language_server_id = LanguageServerId(language_server_id);
372        extension().language_server_command(&language_server_id, worktree)
373    }
374
375    fn language_server_initialization_options(
376        language_server_id: String,
377        worktree: &Worktree,
378    ) -> Result<Option<String>, String> {
379        let language_server_id = LanguageServerId(language_server_id);
380        Ok(extension()
381            .language_server_initialization_options(&language_server_id, worktree)?
382            .and_then(|value| serde_json::to_string(&value).ok()))
383    }
384
385    fn language_server_workspace_configuration(
386        language_server_id: String,
387        worktree: &Worktree,
388    ) -> Result<Option<String>, String> {
389        let language_server_id = LanguageServerId(language_server_id);
390        Ok(extension()
391            .language_server_workspace_configuration(&language_server_id, worktree)?
392            .and_then(|value| serde_json::to_string(&value).ok()))
393    }
394
395    fn language_server_initialization_options_schema(
396        language_server_id: String,
397        worktree: &Worktree,
398    ) -> Option<String> {
399        let language_server_id = LanguageServerId(language_server_id);
400        extension()
401            .language_server_initialization_options_schema(&language_server_id, worktree)
402            .and_then(|value| serde_json::to_string(&value).ok())
403    }
404
405    fn language_server_workspace_configuration_schema(
406        language_server_id: String,
407        worktree: &Worktree,
408    ) -> Option<String> {
409        let language_server_id = LanguageServerId(language_server_id);
410        extension()
411            .language_server_workspace_configuration_schema(&language_server_id, worktree)
412            .and_then(|value| serde_json::to_string(&value).ok())
413    }
414
415    fn language_server_additional_initialization_options(
416        language_server_id: String,
417        target_language_server_id: String,
418        worktree: &Worktree,
419    ) -> Result<Option<String>, String> {
420        let language_server_id = LanguageServerId(language_server_id);
421        let target_language_server_id = LanguageServerId(target_language_server_id);
422        Ok(extension()
423            .language_server_additional_initialization_options(
424                &language_server_id,
425                &target_language_server_id,
426                worktree,
427            )?
428            .and_then(|value| serde_json::to_string(&value).ok()))
429    }
430
431    fn language_server_additional_workspace_configuration(
432        language_server_id: String,
433        target_language_server_id: String,
434        worktree: &Worktree,
435    ) -> Result<Option<String>, String> {
436        let language_server_id = LanguageServerId(language_server_id);
437        let target_language_server_id = LanguageServerId(target_language_server_id);
438        Ok(extension()
439            .language_server_additional_workspace_configuration(
440                &language_server_id,
441                &target_language_server_id,
442                worktree,
443            )?
444            .and_then(|value| serde_json::to_string(&value).ok()))
445    }
446
447    fn labels_for_completions(
448        language_server_id: String,
449        completions: Vec<Completion>,
450    ) -> Result<Vec<Option<CodeLabel>>, String> {
451        let language_server_id = LanguageServerId(language_server_id);
452        let mut labels = Vec::new();
453        for (ix, completion) in completions.into_iter().enumerate() {
454            let label = extension().label_for_completion(&language_server_id, completion);
455            if let Some(label) = label {
456                labels.resize(ix + 1, None);
457                *labels.last_mut().unwrap() = Some(label);
458            }
459        }
460        Ok(labels)
461    }
462
463    fn labels_for_symbols(
464        language_server_id: String,
465        symbols: Vec<Symbol>,
466    ) -> Result<Vec<Option<CodeLabel>>, String> {
467        let language_server_id = LanguageServerId(language_server_id);
468        let mut labels = Vec::new();
469        for (ix, symbol) in symbols.into_iter().enumerate() {
470            let label = extension().label_for_symbol(&language_server_id, symbol);
471            if let Some(label) = label {
472                labels.resize(ix + 1, None);
473                *labels.last_mut().unwrap() = Some(label);
474            }
475        }
476        Ok(labels)
477    }
478
479    fn complete_slash_command_argument(
480        command: SlashCommand,
481        args: Vec<String>,
482    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
483        extension().complete_slash_command_argument(command, args)
484    }
485
486    fn run_slash_command(
487        command: SlashCommand,
488        args: Vec<String>,
489        worktree: Option<&Worktree>,
490    ) -> Result<SlashCommandOutput, String> {
491        extension().run_slash_command(command, args, worktree)
492    }
493
494    fn context_server_command(
495        context_server_id: String,
496        project: &Project,
497    ) -> Result<wit::Command> {
498        let context_server_id = ContextServerId(context_server_id);
499        extension().context_server_command(&context_server_id, project)
500    }
501
502    fn context_server_configuration(
503        context_server_id: String,
504        project: &Project,
505    ) -> Result<Option<ContextServerConfiguration>, String> {
506        let context_server_id = ContextServerId(context_server_id);
507        extension().context_server_configuration(&context_server_id, project)
508    }
509
510    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
511        extension().suggest_docs_packages(provider)
512    }
513
514    fn index_docs(
515        provider: String,
516        package: String,
517        database: &KeyValueStore,
518    ) -> Result<(), String> {
519        extension().index_docs(provider, package, database)
520    }
521
522    fn get_dap_binary(
523        adapter_name: String,
524        config: DebugTaskDefinition,
525        user_installed_path: Option<String>,
526        worktree: &Worktree,
527    ) -> Result<wit::DebugAdapterBinary, String> {
528        extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
529    }
530
531    fn dap_request_kind(
532        adapter_name: String,
533        config: String,
534    ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
535        extension().dap_request_kind(
536            adapter_name,
537            serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
538        )
539    }
540    fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
541        extension().dap_config_to_scenario(config)
542    }
543    fn dap_locator_create_scenario(
544        locator_name: String,
545        build_task: TaskTemplate,
546        resolved_label: String,
547        debug_adapter_name: String,
548    ) -> Option<DebugScenario> {
549        extension().dap_locator_create_scenario(
550            locator_name,
551            build_task,
552            resolved_label,
553            debug_adapter_name,
554        )
555    }
556    fn run_dap_locator(
557        locator_name: String,
558        build_task: TaskTemplate,
559    ) -> Result<DebugRequest, String> {
560        extension().run_dap_locator(locator_name, build_task)
561    }
562}
563
564/// The ID of a language server.
565#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
566pub struct LanguageServerId(String);
567
568impl LanguageServerId {
569    pub fn new(value: String) -> Self {
570        Self(value)
571    }
572}
573
574impl AsRef<str> for LanguageServerId {
575    fn as_ref(&self) -> &str {
576        &self.0
577    }
578}
579
580impl fmt::Display for LanguageServerId {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        write!(f, "{}", self.0)
583    }
584}
585
586/// The ID of a context server.
587#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
588pub struct ContextServerId(String);
589
590impl ContextServerId {
591    pub fn new(value: String) -> Self {
592        Self(value)
593    }
594}
595
596impl AsRef<str> for ContextServerId {
597    fn as_ref(&self) -> &str {
598        &self.0
599    }
600}
601
602impl fmt::Display for ContextServerId {
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        write!(f, "{}", self.0)
605    }
606}
607
608impl CodeLabelSpan {
609    /// Returns a [`CodeLabelSpan::CodeRange`].
610    pub fn code_range(range: impl Into<wit::Range>) -> Self {
611        Self::CodeRange(range.into())
612    }
613
614    /// Returns a [`CodeLabelSpan::Literal`].
615    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
616        Self::Literal(CodeLabelSpanLiteral {
617            text: text.into(),
618            highlight_name,
619        })
620    }
621}
622
623impl From<std::ops::Range<u32>> for wit::Range {
624    fn from(value: std::ops::Range<u32>) -> Self {
625        Self {
626            start: value.start,
627            end: value.end,
628        }
629    }
630}
631
632impl From<std::ops::Range<usize>> for wit::Range {
633    fn from(value: std::ops::Range<usize>) -> Self {
634        Self {
635            start: value.start as u32,
636            end: value.end as u32,
637        }
638    }
639}