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