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 initialization options to pass to the other language server.
104    fn language_server_additional_initialization_options(
105        &mut self,
106        _language_server_id: &LanguageServerId,
107        _target_language_server_id: &LanguageServerId,
108        _worktree: &Worktree,
109    ) -> Result<Option<serde_json::Value>> {
110        Ok(None)
111    }
112
113    /// Returns the workspace configuration options to pass to the other language server.
114    fn language_server_additional_workspace_configuration(
115        &mut self,
116        _language_server_id: &LanguageServerId,
117        _target_language_server_id: &LanguageServerId,
118        _worktree: &Worktree,
119    ) -> Result<Option<serde_json::Value>> {
120        Ok(None)
121    }
122
123    /// Returns the label for the given completion.
124    fn label_for_completion(
125        &self,
126        _language_server_id: &LanguageServerId,
127        _completion: Completion,
128    ) -> Option<CodeLabel> {
129        None
130    }
131
132    /// Returns the label for the given symbol.
133    fn label_for_symbol(
134        &self,
135        _language_server_id: &LanguageServerId,
136        _symbol: Symbol,
137    ) -> Option<CodeLabel> {
138        None
139    }
140
141    /// Returns the completions that should be shown when completing the provided slash command with the given query.
142    fn complete_slash_command_argument(
143        &self,
144        _command: SlashCommand,
145        _args: Vec<String>,
146    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
147        Ok(Vec::new())
148    }
149
150    /// Returns the output from running the provided slash command.
151    fn run_slash_command(
152        &self,
153        _command: SlashCommand,
154        _args: Vec<String>,
155        _worktree: Option<&Worktree>,
156    ) -> Result<SlashCommandOutput, String> {
157        Err("`run_slash_command` not implemented".to_string())
158    }
159
160    /// Returns the command used to start a context server.
161    fn context_server_command(
162        &mut self,
163        _context_server_id: &ContextServerId,
164        _project: &Project,
165    ) -> Result<Command> {
166        Err("`context_server_command` not implemented".to_string())
167    }
168
169    /// Returns the configuration options for the specified context server.
170    fn context_server_configuration(
171        &mut self,
172        _context_server_id: &ContextServerId,
173        _project: &Project,
174    ) -> Result<Option<ContextServerConfiguration>> {
175        Ok(None)
176    }
177
178    /// Returns a list of package names as suggestions to be included in the
179    /// search results of the `/docs` slash command.
180    ///
181    /// This can be used to provide completions for known packages (e.g., from the
182    /// local project or a registry) before a package has been indexed.
183    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
184        Ok(Vec::new())
185    }
186
187    /// Indexes the docs for the specified package.
188    fn index_docs(
189        &self,
190        _provider: String,
191        _package: String,
192        _database: &KeyValueStore,
193    ) -> Result<(), String> {
194        Err("`index_docs` not implemented".to_string())
195    }
196
197    /// Returns the debug adapter binary for the specified adapter name and configuration.
198    fn get_dap_binary(
199        &mut self,
200        _adapter_name: String,
201        _config: DebugTaskDefinition,
202        _user_provided_debug_adapter_path: Option<String>,
203        _worktree: &Worktree,
204    ) -> Result<DebugAdapterBinary, String> {
205        Err("`get_dap_binary` not implemented".to_string())
206    }
207
208    /// Determines whether the specified adapter configuration should *launch* a new debuggee process
209    /// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
210    /// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
211    fn dap_request_kind(
212        &mut self,
213        _adapter_name: String,
214        _config: serde_json::Value,
215    ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
216        Err("`dap_request_kind` not implemented".to_string())
217    }
218    /// 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.
219    ///
220    /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
221    /// create a configuration that can be used to start a debug session.
222    fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
223        Err("`dap_config_to_scenario` not implemented".to_string())
224    }
225
226    /// Locators are entities that convert a Zed task into a debug scenario.
227    ///
228    /// They can be provided even by extensions that don't provide a debug adapter.
229    /// 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.
230    /// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
231    /// be resolved with [`Extension::run_dap_locator`].
232    ///
233    /// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
234    /// 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
235    ///    `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
236    /// 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
237    ///    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
238    ///    found the artifact path by themselves.
239    ///
240    /// 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
241    /// `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.
242    /// This might be of particular relevance to interpreted languages.
243    fn dap_locator_create_scenario(
244        &mut self,
245        _locator_name: String,
246        _build_task: TaskTemplate,
247        _resolved_label: String,
248        _debug_adapter_name: String,
249    ) -> Option<DebugScenario> {
250        None
251    }
252
253    /// Runs the second phase of locator resolution.
254    /// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
255    fn run_dap_locator(
256        &mut self,
257        _locator_name: String,
258        _build_task: TaskTemplate,
259    ) -> Result<DebugRequest, String> {
260        Err("`run_dap_locator` not implemented".to_string())
261    }
262}
263
264/// Registers the provided type as a Zed extension.
265///
266/// The type must implement the [`Extension`] trait.
267#[macro_export]
268macro_rules! register_extension {
269    ($extension_type:ty) => {
270        #[cfg(target_os = "wasi")]
271        mod wasi_ext {
272            unsafe extern "C" {
273                static mut errno: i32;
274                pub static mut __wasilibc_cwd: *mut std::ffi::c_char;
275            }
276
277            pub fn init_cwd() {
278                unsafe {
279                    // Ensure that our chdir function is linked, instead of the
280                    // one from wasi-libc in the chdir.o translation unit. Otherwise
281                    // we risk linking in `__wasilibc_find_relpath_alloc` which
282                    // is a weak symbol and is being used by
283                    // `__wasilibc_find_relpath`, which we do not want on
284                    // Windows.
285                    chdir(std::ptr::null());
286
287                    __wasilibc_cwd = std::ffi::CString::new(std::env::var("PWD").unwrap())
288                        .unwrap()
289                        .into_raw()
290                        .cast();
291                }
292            }
293
294            #[unsafe(no_mangle)]
295            pub unsafe extern "C" fn chdir(raw_path: *const std::ffi::c_char) -> i32 {
296                // Forbid extensions from changing CWD and so return an appropriate error code.
297                errno = 58; // NOTSUP
298                return -1;
299            }
300        }
301
302        #[unsafe(export_name = "init-extension")]
303        pub extern "C" fn __init_extension() {
304            #[cfg(target_os = "wasi")]
305            wasi_ext::init_cwd();
306
307            zed_extension_api::register_extension(|| {
308                Box::new(<$extension_type as zed_extension_api::Extension>::new())
309            });
310        }
311    };
312}
313
314#[doc(hidden)]
315pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
316    unsafe { EXTENSION = Some((build_extension)()) }
317}
318
319fn extension() -> &'static mut dyn Extension {
320    #[expect(static_mut_refs)]
321    unsafe {
322        EXTENSION.as_deref_mut().unwrap()
323    }
324}
325
326static mut EXTENSION: Option<Box<dyn Extension>> = None;
327
328#[cfg(target_arch = "wasm32")]
329#[unsafe(link_section = "zed:api-version")]
330#[doc(hidden)]
331pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
332
333mod wit {
334    wit_bindgen::generate!({
335        skip: ["init-extension"],
336        path: "./wit/since_v0.8.0",
337    });
338}
339
340wit::export!(Component);
341
342struct Component;
343
344impl wit::Guest for Component {
345    fn language_server_command(
346        language_server_id: String,
347        worktree: &wit::Worktree,
348    ) -> Result<wit::Command> {
349        let language_server_id = LanguageServerId(language_server_id);
350        extension().language_server_command(&language_server_id, worktree)
351    }
352
353    fn language_server_initialization_options(
354        language_server_id: String,
355        worktree: &Worktree,
356    ) -> Result<Option<String>, String> {
357        let language_server_id = LanguageServerId(language_server_id);
358        Ok(extension()
359            .language_server_initialization_options(&language_server_id, worktree)?
360            .and_then(|value| serde_json::to_string(&value).ok()))
361    }
362
363    fn language_server_workspace_configuration(
364        language_server_id: String,
365        worktree: &Worktree,
366    ) -> Result<Option<String>, String> {
367        let language_server_id = LanguageServerId(language_server_id);
368        Ok(extension()
369            .language_server_workspace_configuration(&language_server_id, worktree)?
370            .and_then(|value| serde_json::to_string(&value).ok()))
371    }
372
373    fn language_server_additional_initialization_options(
374        language_server_id: String,
375        target_language_server_id: String,
376        worktree: &Worktree,
377    ) -> Result<Option<String>, String> {
378        let language_server_id = LanguageServerId(language_server_id);
379        let target_language_server_id = LanguageServerId(target_language_server_id);
380        Ok(extension()
381            .language_server_additional_initialization_options(
382                &language_server_id,
383                &target_language_server_id,
384                worktree,
385            )?
386            .and_then(|value| serde_json::to_string(&value).ok()))
387    }
388
389    fn language_server_additional_workspace_configuration(
390        language_server_id: String,
391        target_language_server_id: String,
392        worktree: &Worktree,
393    ) -> Result<Option<String>, String> {
394        let language_server_id = LanguageServerId(language_server_id);
395        let target_language_server_id = LanguageServerId(target_language_server_id);
396        Ok(extension()
397            .language_server_additional_workspace_configuration(
398                &language_server_id,
399                &target_language_server_id,
400                worktree,
401            )?
402            .and_then(|value| serde_json::to_string(&value).ok()))
403    }
404
405    fn labels_for_completions(
406        language_server_id: String,
407        completions: Vec<Completion>,
408    ) -> Result<Vec<Option<CodeLabel>>, String> {
409        let language_server_id = LanguageServerId(language_server_id);
410        let mut labels = Vec::new();
411        for (ix, completion) in completions.into_iter().enumerate() {
412            let label = extension().label_for_completion(&language_server_id, completion);
413            if let Some(label) = label {
414                labels.resize(ix + 1, None);
415                *labels.last_mut().unwrap() = Some(label);
416            }
417        }
418        Ok(labels)
419    }
420
421    fn labels_for_symbols(
422        language_server_id: String,
423        symbols: Vec<Symbol>,
424    ) -> Result<Vec<Option<CodeLabel>>, String> {
425        let language_server_id = LanguageServerId(language_server_id);
426        let mut labels = Vec::new();
427        for (ix, symbol) in symbols.into_iter().enumerate() {
428            let label = extension().label_for_symbol(&language_server_id, symbol);
429            if let Some(label) = label {
430                labels.resize(ix + 1, None);
431                *labels.last_mut().unwrap() = Some(label);
432            }
433        }
434        Ok(labels)
435    }
436
437    fn complete_slash_command_argument(
438        command: SlashCommand,
439        args: Vec<String>,
440    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
441        extension().complete_slash_command_argument(command, args)
442    }
443
444    fn run_slash_command(
445        command: SlashCommand,
446        args: Vec<String>,
447        worktree: Option<&Worktree>,
448    ) -> Result<SlashCommandOutput, String> {
449        extension().run_slash_command(command, args, worktree)
450    }
451
452    fn context_server_command(
453        context_server_id: String,
454        project: &Project,
455    ) -> Result<wit::Command> {
456        let context_server_id = ContextServerId(context_server_id);
457        extension().context_server_command(&context_server_id, project)
458    }
459
460    fn context_server_configuration(
461        context_server_id: String,
462        project: &Project,
463    ) -> Result<Option<ContextServerConfiguration>, String> {
464        let context_server_id = ContextServerId(context_server_id);
465        extension().context_server_configuration(&context_server_id, project)
466    }
467
468    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
469        extension().suggest_docs_packages(provider)
470    }
471
472    fn index_docs(
473        provider: String,
474        package: String,
475        database: &KeyValueStore,
476    ) -> Result<(), String> {
477        extension().index_docs(provider, package, database)
478    }
479
480    fn get_dap_binary(
481        adapter_name: String,
482        config: DebugTaskDefinition,
483        user_installed_path: Option<String>,
484        worktree: &Worktree,
485    ) -> Result<wit::DebugAdapterBinary, String> {
486        extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
487    }
488
489    fn dap_request_kind(
490        adapter_name: String,
491        config: String,
492    ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
493        extension().dap_request_kind(
494            adapter_name,
495            serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
496        )
497    }
498    fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
499        extension().dap_config_to_scenario(config)
500    }
501    fn dap_locator_create_scenario(
502        locator_name: String,
503        build_task: TaskTemplate,
504        resolved_label: String,
505        debug_adapter_name: String,
506    ) -> Option<DebugScenario> {
507        extension().dap_locator_create_scenario(
508            locator_name,
509            build_task,
510            resolved_label,
511            debug_adapter_name,
512        )
513    }
514    fn run_dap_locator(
515        locator_name: String,
516        build_task: TaskTemplate,
517    ) -> Result<DebugRequest, String> {
518        extension().run_dap_locator(locator_name, build_task)
519    }
520}
521
522/// The ID of a language server.
523#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
524pub struct LanguageServerId(String);
525
526impl LanguageServerId {
527    pub fn new(value: String) -> Self {
528        Self(value)
529    }
530}
531
532impl AsRef<str> for LanguageServerId {
533    fn as_ref(&self) -> &str {
534        &self.0
535    }
536}
537
538impl fmt::Display for LanguageServerId {
539    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
540        write!(f, "{}", self.0)
541    }
542}
543
544/// The ID of a context server.
545#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
546pub struct ContextServerId(String);
547
548impl ContextServerId {
549    pub fn new(value: String) -> Self {
550        Self(value)
551    }
552}
553
554impl AsRef<str> for ContextServerId {
555    fn as_ref(&self) -> &str {
556        &self.0
557    }
558}
559
560impl fmt::Display for ContextServerId {
561    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562        write!(f, "{}", self.0)
563    }
564}
565
566impl CodeLabelSpan {
567    /// Returns a [`CodeLabelSpan::CodeRange`].
568    pub fn code_range(range: impl Into<wit::Range>) -> Self {
569        Self::CodeRange(range.into())
570    }
571
572    /// Returns a [`CodeLabelSpan::Literal`].
573    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
574        Self::Literal(CodeLabelSpanLiteral {
575            text: text.into(),
576            highlight_name,
577        })
578    }
579}
580
581impl From<std::ops::Range<u32>> for wit::Range {
582    fn from(value: std::ops::Range<u32>) -> Self {
583        Self {
584            start: value.start,
585            end: value.end,
586        }
587    }
588}
589
590impl From<std::ops::Range<usize>> for wit::Range {
591    fn from(value: std::ops::Range<usize>) -> Self {
592        Self {
593            start: value.start as u32,
594            end: value.end as u32,
595        }
596    }
597}