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