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    download_file, make_file_executable,
 19    zed::extension::github::{
 20        github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset,
 21        GithubReleaseOptions,
 22    },
 23    zed::extension::nodejs::{
 24        node_binary_path, npm_install_package, npm_package_installed_version,
 25        npm_package_latest_version,
 26    },
 27    zed::extension::platform::{current_platform, Architecture, Os},
 28    zed::extension::slash_command::{
 29        SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
 30    },
 31    CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
 32    KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree,
 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 label for the given completion.
 97    fn label_for_completion(
 98        &self,
 99        _language_server_id: &LanguageServerId,
100        _completion: Completion,
101    ) -> Option<CodeLabel> {
102        None
103    }
104
105    /// Returns the label for the given symbol.
106    fn label_for_symbol(
107        &self,
108        _language_server_id: &LanguageServerId,
109        _symbol: Symbol,
110    ) -> Option<CodeLabel> {
111        None
112    }
113
114    /// Returns the completions that should be shown when completing the provided slash command with the given query.
115    fn complete_slash_command_argument(
116        &self,
117        _command: SlashCommand,
118        _args: Vec<String>,
119    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
120        Ok(Vec::new())
121    }
122
123    /// Returns the output from running the provided slash command.
124    fn run_slash_command(
125        &self,
126        _command: SlashCommand,
127        _args: Vec<String>,
128        _worktree: Option<&Worktree>,
129    ) -> Result<SlashCommandOutput, String> {
130        Err("`run_slash_command` not implemented".to_string())
131    }
132
133    /// Returns the command used to start a context server.
134    fn context_server_command(
135        &mut self,
136        _context_server_id: &ContextServerId,
137        _project: &Project,
138    ) -> Result<Command> {
139        Err("`context_server_command` not implemented".to_string())
140    }
141
142    /// Returns a list of package names as suggestions to be included in the
143    /// search results of the `/docs` slash command.
144    ///
145    /// This can be used to provide completions for known packages (e.g., from the
146    /// local project or a registry) before a package has been indexed.
147    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
148        Ok(Vec::new())
149    }
150
151    /// Indexes the docs for the specified package.
152    fn index_docs(
153        &self,
154        _provider: String,
155        _package: String,
156        _database: &KeyValueStore,
157    ) -> Result<(), String> {
158        Err("`index_docs` not implemented".to_string())
159    }
160}
161
162/// Registers the provided type as a Zed extension.
163///
164/// The type must implement the [`Extension`] trait.
165#[macro_export]
166macro_rules! register_extension {
167    ($extension_type:ty) => {
168        #[export_name = "init-extension"]
169        pub extern "C" fn __init_extension() {
170            std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
171            zed_extension_api::register_extension(|| {
172                Box::new(<$extension_type as zed_extension_api::Extension>::new())
173            });
174        }
175    };
176}
177
178#[doc(hidden)]
179pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
180    unsafe { EXTENSION = Some((build_extension)()) }
181}
182
183fn extension() -> &'static mut dyn Extension {
184    #[expect(static_mut_refs)]
185    unsafe {
186        EXTENSION.as_deref_mut().unwrap()
187    }
188}
189
190static mut EXTENSION: Option<Box<dyn Extension>> = None;
191
192#[cfg(target_arch = "wasm32")]
193#[link_section = "zed:api-version"]
194#[doc(hidden)]
195pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
196
197mod wit {
198
199    wit_bindgen::generate!({
200        skip: ["init-extension"],
201        path: "./wit/since_v0.3.0",
202    });
203}
204
205wit::export!(Component);
206
207struct Component;
208
209impl wit::Guest for Component {
210    fn language_server_command(
211        language_server_id: String,
212        worktree: &wit::Worktree,
213    ) -> Result<wit::Command> {
214        let language_server_id = LanguageServerId(language_server_id);
215        extension().language_server_command(&language_server_id, worktree)
216    }
217
218    fn language_server_initialization_options(
219        language_server_id: String,
220        worktree: &Worktree,
221    ) -> Result<Option<String>, String> {
222        let language_server_id = LanguageServerId(language_server_id);
223        Ok(extension()
224            .language_server_initialization_options(&language_server_id, worktree)?
225            .and_then(|value| serde_json::to_string(&value).ok()))
226    }
227
228    fn language_server_workspace_configuration(
229        language_server_id: String,
230        worktree: &Worktree,
231    ) -> Result<Option<String>, String> {
232        let language_server_id = LanguageServerId(language_server_id);
233        Ok(extension()
234            .language_server_workspace_configuration(&language_server_id, worktree)?
235            .and_then(|value| serde_json::to_string(&value).ok()))
236    }
237
238    fn labels_for_completions(
239        language_server_id: String,
240        completions: Vec<Completion>,
241    ) -> Result<Vec<Option<CodeLabel>>, String> {
242        let language_server_id = LanguageServerId(language_server_id);
243        let mut labels = Vec::new();
244        for (ix, completion) in completions.into_iter().enumerate() {
245            let label = extension().label_for_completion(&language_server_id, completion);
246            if let Some(label) = label {
247                labels.resize(ix + 1, None);
248                *labels.last_mut().unwrap() = Some(label);
249            }
250        }
251        Ok(labels)
252    }
253
254    fn labels_for_symbols(
255        language_server_id: String,
256        symbols: Vec<Symbol>,
257    ) -> Result<Vec<Option<CodeLabel>>, String> {
258        let language_server_id = LanguageServerId(language_server_id);
259        let mut labels = Vec::new();
260        for (ix, symbol) in symbols.into_iter().enumerate() {
261            let label = extension().label_for_symbol(&language_server_id, symbol);
262            if let Some(label) = label {
263                labels.resize(ix + 1, None);
264                *labels.last_mut().unwrap() = Some(label);
265            }
266        }
267        Ok(labels)
268    }
269
270    fn complete_slash_command_argument(
271        command: SlashCommand,
272        args: Vec<String>,
273    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
274        extension().complete_slash_command_argument(command, args)
275    }
276
277    fn run_slash_command(
278        command: SlashCommand,
279        args: Vec<String>,
280        worktree: Option<&Worktree>,
281    ) -> Result<SlashCommandOutput, String> {
282        extension().run_slash_command(command, args, worktree)
283    }
284
285    fn context_server_command(
286        context_server_id: String,
287        project: &Project,
288    ) -> Result<wit::Command> {
289        let context_server_id = ContextServerId(context_server_id);
290        extension().context_server_command(&context_server_id, project)
291    }
292
293    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
294        extension().suggest_docs_packages(provider)
295    }
296
297    fn index_docs(
298        provider: String,
299        package: String,
300        database: &KeyValueStore,
301    ) -> Result<(), String> {
302        extension().index_docs(provider, package, database)
303    }
304}
305
306/// The ID of a language server.
307#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
308pub struct LanguageServerId(String);
309
310impl AsRef<str> for LanguageServerId {
311    fn as_ref(&self) -> &str {
312        &self.0
313    }
314}
315
316impl fmt::Display for LanguageServerId {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        write!(f, "{}", self.0)
319    }
320}
321
322/// The ID of a context server.
323#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
324pub struct ContextServerId(String);
325
326impl AsRef<str> for ContextServerId {
327    fn as_ref(&self) -> &str {
328        &self.0
329    }
330}
331
332impl fmt::Display for ContextServerId {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        write!(f, "{}", self.0)
335    }
336}
337
338impl CodeLabelSpan {
339    /// Returns a [`CodeLabelSpan::CodeRange`].
340    pub fn code_range(range: impl Into<wit::Range>) -> Self {
341        Self::CodeRange(range.into())
342    }
343
344    /// Returns a [`CodeLabelSpan::Literal`].
345    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
346        Self::Literal(CodeLabelSpanLiteral {
347            text: text.into(),
348            highlight_name,
349        })
350    }
351}
352
353impl From<std::ops::Range<u32>> for wit::Range {
354    fn from(value: std::ops::Range<u32>) -> Self {
355        Self {
356            start: value.start,
357            end: value.end,
358        }
359    }
360}
361
362impl From<std::ops::Range<usize>> for wit::Range {
363    fn from(value: std::ops::Range<usize>) -> Self {
364        Self {
365            start: value.start as u32,
366            end: value.end as u32,
367        }
368    }
369}