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    #![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
199
200    wit_bindgen::generate!({
201        skip: ["init-extension"],
202        path: "./wit/since_v0.3.0",
203    });
204}
205
206wit::export!(Component);
207
208struct Component;
209
210impl wit::Guest for Component {
211    fn language_server_command(
212        language_server_id: String,
213        worktree: &wit::Worktree,
214    ) -> Result<wit::Command> {
215        let language_server_id = LanguageServerId(language_server_id);
216        extension().language_server_command(&language_server_id, worktree)
217    }
218
219    fn language_server_initialization_options(
220        language_server_id: String,
221        worktree: &Worktree,
222    ) -> Result<Option<String>, String> {
223        let language_server_id = LanguageServerId(language_server_id);
224        Ok(extension()
225            .language_server_initialization_options(&language_server_id, worktree)?
226            .and_then(|value| serde_json::to_string(&value).ok()))
227    }
228
229    fn language_server_workspace_configuration(
230        language_server_id: String,
231        worktree: &Worktree,
232    ) -> Result<Option<String>, String> {
233        let language_server_id = LanguageServerId(language_server_id);
234        Ok(extension()
235            .language_server_workspace_configuration(&language_server_id, worktree)?
236            .and_then(|value| serde_json::to_string(&value).ok()))
237    }
238
239    fn labels_for_completions(
240        language_server_id: String,
241        completions: Vec<Completion>,
242    ) -> Result<Vec<Option<CodeLabel>>, String> {
243        let language_server_id = LanguageServerId(language_server_id);
244        let mut labels = Vec::new();
245        for (ix, completion) in completions.into_iter().enumerate() {
246            let label = extension().label_for_completion(&language_server_id, completion);
247            if let Some(label) = label {
248                labels.resize(ix + 1, None);
249                *labels.last_mut().unwrap() = Some(label);
250            }
251        }
252        Ok(labels)
253    }
254
255    fn labels_for_symbols(
256        language_server_id: String,
257        symbols: Vec<Symbol>,
258    ) -> Result<Vec<Option<CodeLabel>>, String> {
259        let language_server_id = LanguageServerId(language_server_id);
260        let mut labels = Vec::new();
261        for (ix, symbol) in symbols.into_iter().enumerate() {
262            let label = extension().label_for_symbol(&language_server_id, symbol);
263            if let Some(label) = label {
264                labels.resize(ix + 1, None);
265                *labels.last_mut().unwrap() = Some(label);
266            }
267        }
268        Ok(labels)
269    }
270
271    fn complete_slash_command_argument(
272        command: SlashCommand,
273        args: Vec<String>,
274    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
275        extension().complete_slash_command_argument(command, args)
276    }
277
278    fn run_slash_command(
279        command: SlashCommand,
280        args: Vec<String>,
281        worktree: Option<&Worktree>,
282    ) -> Result<SlashCommandOutput, String> {
283        extension().run_slash_command(command, args, worktree)
284    }
285
286    fn context_server_command(
287        context_server_id: String,
288        project: &Project,
289    ) -> Result<wit::Command> {
290        let context_server_id = ContextServerId(context_server_id);
291        extension().context_server_command(&context_server_id, project)
292    }
293
294    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
295        extension().suggest_docs_packages(provider)
296    }
297
298    fn index_docs(
299        provider: String,
300        package: String,
301        database: &KeyValueStore,
302    ) -> Result<(), String> {
303        extension().index_docs(provider, package, database)
304    }
305}
306
307/// The ID of a language server.
308#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
309pub struct LanguageServerId(String);
310
311impl AsRef<str> for LanguageServerId {
312    fn as_ref(&self) -> &str {
313        &self.0
314    }
315}
316
317impl fmt::Display for LanguageServerId {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        write!(f, "{}", self.0)
320    }
321}
322
323/// The ID of a context server.
324#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
325pub struct ContextServerId(String);
326
327impl AsRef<str> for ContextServerId {
328    fn as_ref(&self) -> &str {
329        &self.0
330    }
331}
332
333impl fmt::Display for ContextServerId {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        write!(f, "{}", self.0)
336    }
337}
338
339impl CodeLabelSpan {
340    /// Returns a [`CodeLabelSpan::CodeRange`].
341    pub fn code_range(range: impl Into<wit::Range>) -> Self {
342        Self::CodeRange(range.into())
343    }
344
345    /// Returns a [`CodeLabelSpan::Literal`].
346    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
347        Self::Literal(CodeLabelSpanLiteral {
348            text: text.into(),
349            highlight_name,
350        })
351    }
352}
353
354impl From<std::ops::Range<u32>> for wit::Range {
355    fn from(value: std::ops::Range<u32>) -> Self {
356        Self {
357            start: value.start,
358            end: value.end,
359        }
360    }
361}
362
363impl From<std::ops::Range<usize>> for wit::Range {
364    fn from(value: std::ops::Range<usize>) -> Self {
365        Self {
366            start: value.start as u32,
367            end: value.end as u32,
368        }
369    }
370}